mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
enh: add exit-hook support
This commit is contained in:
parent
89e8d96c16
commit
4085a73c25
@ -31,6 +31,7 @@
|
|||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"event-source-polyfill": "^1.0.31",
|
"event-source-polyfill": "^1.0.31",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
|
"exit-hook": "^4.0.0",
|
||||||
"ftp-srv": "^4.6.2",
|
"ftp-srv": "^4.6.2",
|
||||||
"get-port": "^6.1.2",
|
"get-port": "^6.1.2",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
|
@ -110,19 +110,5 @@ global.EventSource = require('eventsource')
|
|||||||
|
|
||||||
info(`Hooking into process exit event`)
|
info(`Hooking into process exit event`)
|
||||||
|
|
||||||
const shutdown = async (signal: NodeJS.Signals) => {
|
|
||||||
info(`Got signal ${signal}`)
|
|
||||||
info(`Shutting down`)
|
|
||||||
ftpService().shutdown()
|
|
||||||
;(await realtimeLog()).shutdown()
|
|
||||||
;(await proxyService()).shutdown()
|
|
||||||
;(await instanceService()).shutdown()
|
|
||||||
;(await rpcService()).shutdown()
|
|
||||||
pbService.shutdown()
|
|
||||||
}
|
|
||||||
|
|
||||||
await (await rpcService()).initRpcs()
|
await (await rpcService()).initRpcs()
|
||||||
process.on('SIGTERM', shutdown)
|
|
||||||
process.on('SIGINT', shutdown)
|
|
||||||
process.on('SIGHUP', shutdown)
|
|
||||||
})()
|
})()
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
SSL_KEY,
|
SSL_KEY,
|
||||||
} from '$constants'
|
} from '$constants'
|
||||||
import { clientService, createPbClient } from '$services'
|
import { clientService, createPbClient } from '$services'
|
||||||
|
import { exitHook } from '$util'
|
||||||
import { SingletonBaseConfig, mkSingleton } from '@pockethost/common'
|
import { SingletonBaseConfig, mkSingleton } from '@pockethost/common'
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { FtpSrv } from 'ftp-srv'
|
import { FtpSrv } from 'ftp-srv'
|
||||||
@ -81,10 +82,10 @@ export const ftpService = mkSingleton((config: FtpConfig) => {
|
|||||||
info('Ftp server started...')
|
info('Ftp server started...')
|
||||||
})
|
})
|
||||||
|
|
||||||
const shutdown = () => {
|
exitHook(() => {
|
||||||
info(`Closing FTP server`)
|
info(`Closing FTP server`)
|
||||||
ftpServer.close()
|
ftpServer.close()
|
||||||
}
|
})
|
||||||
|
|
||||||
return { shutdown }
|
return {}
|
||||||
})
|
})
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
port,
|
port,
|
||||||
proxyService,
|
proxyService,
|
||||||
} from '$services'
|
} from '$services'
|
||||||
import { mkInternalUrl, now } from '$util'
|
import { asyncExitHook, mkInternalUrl, now } from '$util'
|
||||||
import {
|
import {
|
||||||
assertTruthy,
|
assertTruthy,
|
||||||
CLEANUP_PRIORITY_LAST,
|
CLEANUP_PRIORITY_LAST,
|
||||||
@ -165,6 +165,7 @@ export const instanceService = mkSingleton(
|
|||||||
return startRequest()
|
return startRequest()
|
||||||
},
|
},
|
||||||
shutdown: async (reason) => {
|
shutdown: async (reason) => {
|
||||||
|
dbg(`Shutting down`)
|
||||||
if (reason) {
|
if (reason) {
|
||||||
_shutdownReason = reason
|
_shutdownReason = reason
|
||||||
error(`Panic shutdown for ${reason}`)
|
error(`Panic shutdown for ${reason}`)
|
||||||
@ -462,14 +463,14 @@ export const instanceService = mkSingleton(
|
|||||||
`InstanceService`,
|
`InstanceService`,
|
||||||
)
|
)
|
||||||
|
|
||||||
const shutdown = async () => {
|
asyncExitHook(async () => {
|
||||||
dbg(`Shutting down instance manager`)
|
dbg(`Shutting down instance manager`)
|
||||||
const p = Promise.all(map(instanceApis, (api) => api.shutdown()))
|
const p = Promise.all(map(instanceApis, (api) => api.shutdown()))
|
||||||
await p
|
await p
|
||||||
}
|
})
|
||||||
|
|
||||||
const getInstanceApiIfExistsById = (id: InstanceId) => instanceApis[id]
|
const getInstanceApiIfExistsById = (id: InstanceId) => instanceApis[id]
|
||||||
|
|
||||||
return { shutdown, getInstanceApiIfExistsById }
|
return { getInstanceApiIfExistsById }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,13 @@ import {
|
|||||||
PUBLIC_DEBUG,
|
PUBLIC_DEBUG,
|
||||||
} from '$constants'
|
} from '$constants'
|
||||||
import { port as getPort, InstanceLogger, updaterService } from '$services'
|
import { port as getPort, InstanceLogger, updaterService } from '$services'
|
||||||
import { assert, AsyncContext, mkInternalUrl, tryFetch } from '$util'
|
import {
|
||||||
|
assert,
|
||||||
|
AsyncContext,
|
||||||
|
asyncExitHook,
|
||||||
|
mkInternalUrl,
|
||||||
|
tryFetch,
|
||||||
|
} from '$util'
|
||||||
import {
|
import {
|
||||||
createCleanupManager,
|
createCleanupManager,
|
||||||
createTimerManager,
|
createTimerManager,
|
||||||
@ -58,10 +64,10 @@ export const createPocketbaseService = async (
|
|||||||
const { getLatestVersion, getVersion } = await updaterService()
|
const { getLatestVersion, getVersion } = await updaterService()
|
||||||
const maxVersion = getLatestVersion()
|
const maxVersion = getLatestVersion()
|
||||||
|
|
||||||
const cm = createCleanupManager()
|
|
||||||
const tm = createTimerManager({})
|
const tm = createTimerManager({})
|
||||||
|
|
||||||
const _spawn = async (cfg: SpawnConfig, context?: AsyncContext) => {
|
const _spawn = async (cfg: SpawnConfig, context?: AsyncContext) => {
|
||||||
|
const cm = createCleanupManager()
|
||||||
const logger = (context?.logger || _serviceLogger).create('spawn')
|
const logger = (context?.logger || _serviceLogger).create('spawn')
|
||||||
const { dbg, warn, error } = logger
|
const { dbg, warn, error } = logger
|
||||||
const defaultPort = await (async () => {
|
const defaultPort = await (async () => {
|
||||||
@ -195,6 +201,8 @@ export const createPocketbaseService = async (
|
|||||||
iLogger.info(`${slug} closed with code ${StatusCode}`)
|
iLogger.info(`${slug} closed with code ${StatusCode}`)
|
||||||
dbg({ err, data })
|
dbg({ err, data })
|
||||||
isRunning = false
|
isRunning = false
|
||||||
|
container = undefined
|
||||||
|
unsub()
|
||||||
if (StatusCode > 0 || err) {
|
if (StatusCode > 0 || err) {
|
||||||
iLogger.error(
|
iLogger.error(
|
||||||
`Unexpected stop with code ${StatusCode} and error ${err}`,
|
`Unexpected stop with code ${StatusCode} and error ${err}`,
|
||||||
@ -212,24 +220,7 @@ export const createPocketbaseService = async (
|
|||||||
resolve(container)
|
resolve(container)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
if (container) {
|
if (!container) {
|
||||||
cm.add(async () => {
|
|
||||||
dbg(`Stopping ${slug} for cleanup`)
|
|
||||||
iLogger.info(`Stopping instance`)
|
|
||||||
await container
|
|
||||||
?.stop()
|
|
||||||
.then(() => {
|
|
||||||
iLogger.info(`Instance stopped`)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
iLogger.error(`Error stopping instance`)
|
|
||||||
warn(`Possible error stopping container: ${err}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
stderr.off('data', _stdErrData)
|
|
||||||
stdout.off('data', _stdoutData)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
iLogger.error(`Could not start container`)
|
iLogger.error(`Could not start container`)
|
||||||
error(`${slug} could not start container`)
|
error(`${slug} could not start container`)
|
||||||
onUnexpectedStop?.(999)
|
onUnexpectedStop?.(999)
|
||||||
@ -244,6 +235,10 @@ export const createPocketbaseService = async (
|
|||||||
logger: _serviceLogger,
|
logger: _serviceLogger,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const unsub = asyncExitHook(async () => {
|
||||||
|
dbg(`Exiting process ${slug}`)
|
||||||
|
await api.kill()
|
||||||
|
})
|
||||||
const api: PocketbaseProcess = {
|
const api: PocketbaseProcess = {
|
||||||
url,
|
url,
|
||||||
pid: () => {
|
pid: () => {
|
||||||
@ -252,26 +247,33 @@ export const createPocketbaseService = async (
|
|||||||
},
|
},
|
||||||
exited,
|
exited,
|
||||||
kill: async () => {
|
kill: async () => {
|
||||||
|
unsub()
|
||||||
if (!container) {
|
if (!container) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Attempt to kill a PocketBase process that was never running.`,
|
`Attempt to kill a PocketBase process that was never running.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
iLogger.info(`Stopping instance`)
|
||||||
await container.stop()
|
await container.stop()
|
||||||
|
iLogger.info(`Instance stopped`)
|
||||||
|
stderr.off('data', _stdErrData)
|
||||||
|
stdout.off('data', _stdoutData)
|
||||||
|
|
||||||
|
container = undefined
|
||||||
|
await cm.shutdown()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
const shutdown = () => {
|
asyncExitHook(async () => {
|
||||||
dbg(`Shutting down pocketbaseService`)
|
dbg(`Shutting down pocketbaseService`)
|
||||||
tm.shutdown()
|
tm.shutdown()
|
||||||
cm.shutdown()
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
spawn: _spawn,
|
spawn: _spawn,
|
||||||
shutdown,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { DAEMON_PORT, PUBLIC_EDGE_APEX_DOMAIN } from '$constants'
|
import { DAEMON_PORT, PUBLIC_EDGE_APEX_DOMAIN } from '$constants'
|
||||||
|
import { asyncExitHook } from '$util'
|
||||||
import { Logger, SingletonBaseConfig, mkSingleton } from '@pockethost/common'
|
import { Logger, SingletonBaseConfig, mkSingleton } from '@pockethost/common'
|
||||||
import { isFunction } from '@s-libs/micro-dash'
|
import { isFunction } from '@s-libs/micro-dash'
|
||||||
import {
|
import {
|
||||||
@ -10,7 +11,6 @@ import {
|
|||||||
import { default as Server, default as httpProxy } from 'http-proxy'
|
import { default as Server, default as httpProxy } from 'http-proxy'
|
||||||
import { AsyncReturnType, SetReturnType } from 'type-fest'
|
import { AsyncReturnType, SetReturnType } from 'type-fest'
|
||||||
import UrlPattern from 'url-pattern'
|
import UrlPattern from 'url-pattern'
|
||||||
|
|
||||||
export type ProxyServiceApi = AsyncReturnType<typeof proxyService>
|
export type ProxyServiceApi = AsyncReturnType<typeof proxyService>
|
||||||
|
|
||||||
export type ProxyMiddleware = (
|
export type ProxyMiddleware = (
|
||||||
@ -88,7 +88,7 @@ export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
|||||||
info(`daemon on port ${DAEMON_PORT}`)
|
info(`daemon on port ${DAEMON_PORT}`)
|
||||||
server.listen(DAEMON_PORT)
|
server.listen(DAEMON_PORT)
|
||||||
|
|
||||||
const shutdown = async () => {
|
asyncExitHook(() => {
|
||||||
info(`Shutting down proxy server`)
|
info(`Shutting down proxy server`)
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
server.close((err) => {
|
server.close((err) => {
|
||||||
@ -97,7 +97,7 @@ export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
|||||||
})
|
})
|
||||||
server.closeAllConnections()
|
server.closeAllConnections()
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
|
|
||||||
type MiddlewareListener = SetReturnType<
|
type MiddlewareListener = SetReturnType<
|
||||||
RequestListener,
|
RequestListener,
|
||||||
@ -169,5 +169,5 @@ export const proxyService = mkSingleton(async (config: ProxyServiceConfig) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { shutdown, use }
|
return { use }
|
||||||
})
|
})
|
||||||
|
@ -118,9 +118,5 @@ export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
|
|||||||
`RealtimeLogService`,
|
`RealtimeLogService`,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {}
|
||||||
shutdown() {
|
|
||||||
dbg(`shutdown`)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { isObject } from '@s-libs/micro-dash'
|
import { isObject } from '@s-libs/micro-dash'
|
||||||
import Ajv, { JSONSchemaType, ValidateFunction } from 'ajv'
|
import Ajv, { JSONSchemaType, ValidateFunction } from 'ajv'
|
||||||
import Bottleneck from 'bottleneck'
|
import Bottleneck from 'bottleneck'
|
||||||
|
import exitHook from 'exit-hook'
|
||||||
import { default as knexFactory } from 'knex'
|
import { default as knexFactory } from 'knex'
|
||||||
import pocketbaseEs, { ClientResponseError } from 'pocketbase'
|
import pocketbaseEs, { ClientResponseError } from 'pocketbase'
|
||||||
import { AsyncReturnType, JsonObject } from 'type-fest'
|
import { AsyncReturnType, JsonObject } from 'type-fest'
|
||||||
@ -107,9 +108,7 @@ export const rpcService = mkSingleton(async (config: RpcServiceConfig) => {
|
|||||||
|
|
||||||
const unsub = await client.onNewRpc(run)
|
const unsub = await client.onNewRpc(run)
|
||||||
|
|
||||||
const shutdown = () => {
|
exitHook(unsub)
|
||||||
unsub()
|
|
||||||
}
|
|
||||||
|
|
||||||
const ajv = new Ajv()
|
const ajv = new Ajv()
|
||||||
|
|
||||||
@ -133,6 +132,5 @@ export const rpcService = mkSingleton(async (config: RpcServiceConfig) => {
|
|||||||
return {
|
return {
|
||||||
registerCommand,
|
registerCommand,
|
||||||
initRpcs,
|
initRpcs,
|
||||||
shutdown,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
5
packages/daemon/src/util/exit.ts
Normal file
5
packages/daemon/src/util/exit.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import exitHook, { asyncExitHook as _ } from 'exit-hook'
|
||||||
|
|
||||||
|
const asyncExitHook = (cb: () => Promise<void>) => _(cb, { wait: 1000 })
|
||||||
|
|
||||||
|
export { asyncExitHook, exitHook }
|
@ -4,6 +4,7 @@ export * from './assert'
|
|||||||
export * from './downloadAndExtract'
|
export * from './downloadAndExtract'
|
||||||
export * from './ensureDirExists'
|
export * from './ensureDirExists'
|
||||||
export * from './env'
|
export * from './env'
|
||||||
|
export * from './exit'
|
||||||
export * from './internal'
|
export * from './internal'
|
||||||
export * from './now'
|
export * from './now'
|
||||||
export * from './smartFetch'
|
export * from './smartFetch'
|
||||||
|
@ -2724,6 +2724,11 @@ executable@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
pify "^2.2.0"
|
pify "^2.2.0"
|
||||||
|
|
||||||
|
exit-hook@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-4.0.0.tgz#c1e16ebd03d3166f837b1502dac755bb5c460d58"
|
||||||
|
integrity sha512-Fqs7ChZm72y40wKjOFXBKg7nJZvQJmewP5/7LtePDdnah/+FH9Hp5sgMujSCMPXlxOAW2//1jrW9pnsY7o20vQ==
|
||||||
|
|
||||||
expand-template@^2.0.3:
|
expand-template@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user