gracefully quit even with plugins requiring some extra time
Massimo Melina committed
May 1, 2022 at 17:39 UTC
f4857c8e2e420935a7a5f42f13881c6eabcd7473
5 files changed
+33
-29
plugins/download-counter/plugin.js
+17
-11
@@ -1,25 +1,31 @@
1
exports.description = "Counts downloads for each file, and displays the total in the list"
2
-exports.version = 1
2
+exports.version = 2 // comply to new async init/unload
3
+exports.apiRequired = 3
4
4
-exports.init = api => {
5
+exports.init = async api => {
6
const _ = api.require('lodash')
7
const yaml = api.require('yaml')
7
- const { writeFile, readFile } = api.require('fs')
8
+ const { writeFile, readFile } = api.require('fs/promises')
9
+ const { debounceAsync } = api.require('./misc')
10
11
const countersFile = 'counters.yaml'
12
11
- const counters = {}
12
- const save = _.debounce(() => {
13
- writeFile(countersFile, yaml.stringify(counters), err => console.debug(err || 'counters saved'))
13
+ let counters = {}
14
+ const save = debounceAsync(async () => {
15
+ await writeFile(countersFile, yaml.stringify(counters))
16
+ console.debug('counters saved')
17
}, 5_000, { maxWait:30_000 })
18
19
// load previous stats
17
- readFile(countersFile, 'utf8', (err, data) => {
18
- if (err)
19
- return err.code === 'ENOENT' || console.debug(countersFile, err)
20
- Object.assign(counters, yaml.parse(String(data)))
20
+ try {
21
+ const data = await readFile(countersFile, 'utf8')
22
+ counters = yaml.parse(data)
23
console.debug('counters loaded')
22
- })
24
+ }
25
+ catch(err) {
26
+ if (err.code !== 'ENOENT')
27
+ console.debug(countersFile, err)
28
+ }
29
30
return {
31
frontend_js: 'hits.js',
server/src/const.ts
+1
-1
@@ -12,7 +12,7 @@ export const VERSION = ''
12
export const SESSION_DURATION = 30*60_000
13
export const DAY = 86_400_000
14
15
-export const API_VERSION = 3 // with 3 we introduced config.defaultValue
15
+export const API_VERSION = 3 // introduced config.defaultValue and async for init/unload
16
export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise is made equal to API_VERSION
17
18
export const SPECIAL_URI = '/~/'
server/src/index.ts
+1
-7
@@ -5,7 +5,7 @@ import mount from 'koa-mount'
5
import { apiMiddleware } from './apiMiddleware'
6
import { API_URI, DEV } from './const'
7
import { frontEndApis } from './frontEndApis'
8
-import { debugLog, log } from './log'
8
+import { log } from './log'
9
import { pluginsMiddleware } from './plugins'
10
import { throttler } from './throttler'
11
import { headRequests, gzipper, sessions, serveGuiAndSharedFiles, someSecurity, prepareState } from './middlewares'
@@ -14,7 +14,6 @@ import { adminApis } from './adminApis'
14
import { defineConfig } from './config'
15
import { ok } from 'assert'
16
import _ from 'lodash'
17
-import { onProcessExit } from './misc'
17
18
ok(_.intersection(Object.keys(frontEndApis), Object.keys(adminApis)).length === 0) // they share same endpoints
19
@@ -48,8 +47,3 @@ defineConfig('proxies', 0).sub(n => {
47
app.proxy = n > 0
48
app.maxIpsCount = n
49
})
51
-
52
-onProcessExit(sig => {
53
- debugLog('exit by', sig)
54
- setTimeout(()=> process.exit(0), 1000) // 1-second grace period
55
-})
server/src/misc.ts
+9
-3
@@ -104,9 +104,15 @@ export function randomId(len = 10) {
104
.replace(/l/g, 'L'); // avoid confusion reading l1
105
}
106
107
-export function onProcessExit(cb: (signal:string)=>void) {
108
- onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], cb)
109
-}
107
+type ProcessExitHandler = (signal:string) => any
108
+const cbs = new Set<ProcessExitHandler>()
109
+export function onProcessExit(cb: ProcessExitHandler) {
110
+ cbs.add(cb)
111
+ return () => cbs.delete(cb)
112
+}
113
+onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], signal =>
114
+ Promise.allSettled(Array.from(cbs).map(cb => cb(signal))).then(() =>
115
+ process.exit(0)))
116
117
export function onFirstEvent(emitter:EventEmitter, events: string[], cb: (...args:any[])=> void) {
118
let already = false
server/src/plugins.ts
+5
-7
@@ -119,10 +119,10 @@ export class Plugin {
119
return { ...this.data }
120
}
121
122
- unload() {
122
+ async unload() {
123
const { id } = this
124
console.log('unloading plugin', id)
125
- try { this.data?.unload?.() }
125
+ try { await this.data?.unload?.() }
126
catch(e) {
127
console.debug('error unloading plugin', id, String(e))
128
}
@@ -221,7 +221,7 @@ async function rescan() {
221
}
222
for (const id in plugins)
223
if (!found.includes(id))
224
- plugins[id].unload()
224
+ await plugins[id].unload()
225
}
226
227
function deleteModule(id: string) {
@@ -246,7 +246,5 @@ function deleteModule(id: string) {
246
}
247
}
248
249
-onProcessExit(() => {
250
- for (const pl of Object.values(plugins))
251
- pl.unload()
252
-})
249
+onProcessExit(() =>
250
+ Promise.allSettled(mapPlugins(pl => pl.unload())))