Pithos: s3d and its one-shot commands never open the database at once
After adding another account, opening a page failed with "failed to open database: table users already exists": a users list ran while s3d was starting, and both set up the fresh database. A start now waits for running commands to finish, and commands wait while s3d is starting.
This commit is contained in:
parent
47c0c27f3d
commit
9024a2a217
1 changed files with 39 additions and 9 deletions
|
|
@ -62,12 +62,24 @@ export class Daemon extends EventEmitter {
|
|||
|
||||
logsSince(seq = 0) { return this.logs.filter((l) => l.seq > seq); }
|
||||
|
||||
// s3d migrates its database when it starts, and so does every one-shot
|
||||
// command (users list, keys…) that opens it. Two of them at once failed
|
||||
// with "table users already exists", so a start waits for running
|
||||
// commands to finish, and commands wait while s3d is starting.
|
||||
start() {
|
||||
if (!this.binary) throw new Error('s3d binary not found');
|
||||
if (this.child) return this.status();
|
||||
if (this.child || this.pendingStart) return this.status();
|
||||
this.exitCode = null;
|
||||
this.needsLogin = false;
|
||||
this.setState('starting');
|
||||
if (this.cliRuns > 0) {
|
||||
this.pendingStart = true;
|
||||
this.once('cli-idle', () => { this.pendingStart = false; if (this.state === 'starting' && !this.child) this.spawnServer(); });
|
||||
} else this.spawnServer();
|
||||
return this.status();
|
||||
}
|
||||
|
||||
spawnServer() {
|
||||
const child = spawn(this.binary, [], {
|
||||
cwd: this.cwd(), env: this.env(), stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true,
|
||||
});
|
||||
|
|
@ -91,6 +103,7 @@ export class Daemon extends EventEmitter {
|
|||
|
||||
async stop() {
|
||||
const child = this.child;
|
||||
if (!child && this.pendingStart) { this.pendingStart = false; this.setState('stopped'); return this.status(); }
|
||||
if (!child) return this.status();
|
||||
this.setState('stopping');
|
||||
const exited = new Promise((r) => child.once('exit', r));
|
||||
|
|
@ -103,15 +116,32 @@ export class Daemon extends EventEmitter {
|
|||
|
||||
// One-shot CLI run. Resolves { code, stdout, stderr }; never rejects on a
|
||||
// non-zero exit so callers can surface s3d's own error text.
|
||||
run(args, { timeout = 30_000 } = {}) {
|
||||
if (!this.binary) return Promise.reject(new Error('s3d binary not found'));
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(this.binary, args, {
|
||||
cwd: this.cwd(), env: this.env(), timeout, windowsHide: true, maxBuffer: 16 << 20,
|
||||
}, (err, stdout, stderr) => {
|
||||
if (err && typeof err.code !== 'number') return reject(err);
|
||||
resolve({ code: err ? err.code : 0, stdout: stdout.replace(ANSI, ''), stderr: stderr.replace(ANSI, '') });
|
||||
async run(args, { timeout = 30_000 } = {}) {
|
||||
if (!this.binary) throw new Error('s3d binary not found');
|
||||
if (this.state === 'starting') await this.settled(20_000);
|
||||
this.cliRuns = (this.cliRuns || 0) + 1;
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
execFile(this.binary, args, {
|
||||
cwd: this.cwd(), env: this.env(), timeout, windowsHide: true, maxBuffer: 16 << 20,
|
||||
}, (err, stdout, stderr) => {
|
||||
if (err && typeof err.code !== 'number') return reject(err);
|
||||
resolve({ code: err ? err.code : 0, stdout: stdout.replace(ANSI, ''), stderr: stderr.replace(ANSI, '') });
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
if (--this.cliRuns === 0) this.emit('cli-idle');
|
||||
}
|
||||
}
|
||||
|
||||
// Resolves once s3d is no longer starting (or after ms).
|
||||
settled(ms) {
|
||||
return new Promise((resolve) => {
|
||||
if (this.state !== 'starting') return resolve();
|
||||
const done = () => { clearTimeout(t); this.off('status', on); resolve(); };
|
||||
const on = (st) => { if (st.state !== 'starting') done(); };
|
||||
const t = setTimeout(done, ms);
|
||||
this.on('status', on);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue