fix: (regression 3.1.2) faulty update procedure
Massimo Melina committed
May 3, 2026 at 15:10 UTC
6c39538ca87760b2d84537596b6f069b0ccd50d9
2 files changed
+18
-7
src/first.ts
+14
-4
@@ -13,13 +13,14 @@ export function onProcessExit(cb: ProcessExitHandler, order=10) {
13
}
14
15
export let quitting = false
16
+export let exitCode = 0
17
// 'exit' event is handled as the last resort, but it's not compatible with async callbacks
17
-onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], async signal => {
18
+onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP', 'beforeExit'], async signal => {
19
console.log('Quitting with signal:', signal || 'unknown')
20
quitting = true
21
const byOrder = _.groupBy(Array.from(cbsOnExit), 'order') // this will be inherently ordered because keys are positive integers
21
- for (const recs of Object.values(byOrder))
22
- await Promise.allSettled(recs.map(({ cb }) => {
22
+ for (const recs of Object.values(byOrder)) {
23
+ const ret = Promise.allSettled(recs.map(({ cb }) => {
24
try { return cb(signal) }
25
// keep exit moving even when a synchronous cleanup fails after partially shutting down
26
catch (e) {
@@ -27,10 +28,19 @@ onFirstEvent(process, ['exit', 'SIGQUIT', 'SIGTERM', 'SIGINT', 'SIGHUP'], async
28
return Promise.reject(e)
29
}
30
}))
31
+ if (signal !== 'exit') // exit is sync
32
+ await ret
33
+ }
34
+ cbsOnExit.clear()
35
console.debug('Process exit')
31
- process.exit(0)
36
+ process.exit(exitCode)
37
})
38
39
+export function quit(code=0) {
40
+ exitCode = code
41
+ process.emit('SIGINT')
42
+}
43
+
44
// keep calling cb in a sync fashion – returning a promise instead would break the code for argv.updating (update.ts)
45
export function onFirstEvent(emitter:EventEmitter, events: string[], cb: (...args:any[]) => void) {
46
let already = false
src/update.ts
+4
-3
@@ -11,7 +11,7 @@ import { chmod, rename, writeFile, rm } from 'fs/promises'
11
import open from 'open'
12
import { configReady, currentVersion, defineConfig, versionToScalar } from './config'
13
import { cmdEscape, runningAsWindowsService } from './util-os'
14
-import { onProcessExit } from './first'
14
+import { onProcessExit, quit } from './first'
15
import { storedMap } from './persistence'
16
import _ from 'lodash'
17
import { argv } from './argv'
@@ -64,7 +64,7 @@ const ReleaseAssetKeys = ['name', 'browser_download_url'] satisfies (keyof Relea
64
const curV = currentVersion.scalar
65
function prepareRelease(r: Release) {
66
const v = versionToScalar(r.name)
67
- return Object.assign(_.pick(r, ReleaseKeys), { // prune a bit, as it will be serialized and it has a lot of unused data
67
+ return Object.assign(_.pick(r, ReleaseKeys), { // prune a bit, as it will be serialized, and it has a lot of unused data
68
versionScalar: v,
69
isNewer: v > curV, // make easy to know what's newer
70
assets: r.assets.map((a: any) => _.pick(a, ReleaseAssetKeys))
@@ -197,7 +197,8 @@ export async function update(tagOrUrl: string='') {
197
spawnSync(cmdEscape(newBin), ['--updating', binFile, '--cwd .'], { shell: true, stdio: [0,1,2] }) // sync necessary to work on Mac by double-click
198
})
199
console.log("Quitting")
200
- setTimeout(() => process.exit(100)) // non-zero, otherwise some service managers (like Shawl) won't restart the process
200
+ setTimeout(() => quit(100), // non-zero, otherwise some service managers (like Shawl) won't restart the process.
201
+ 200) // give time to return (and caller to complete, eg: rest api to reply)
202
}
203
catch (e: any) {
204
pluginsWatcher.unpause()