fix: finalize durable Pi lifecycle
This commit is contained in:
@@ -10,17 +10,25 @@ import {
|
||||
} from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
|
||||
export interface MaintenanceDurability {
|
||||
syncDirectory(directory: string): void;
|
||||
}
|
||||
|
||||
/** A durable admission barrier. A lease spans the complete create/resume decision. */
|
||||
export class MaintenanceBarrier {
|
||||
private active: boolean;
|
||||
private admissions = 0;
|
||||
private waiters: (() => void)[] = [];
|
||||
|
||||
constructor(private readonly markerFile?: string) {
|
||||
constructor(
|
||||
private readonly markerFile?: string,
|
||||
private readonly durability: MaintenanceDurability = defaultDurability,
|
||||
) {
|
||||
this.active = markerFile === undefined ? false : existsSync(markerFile);
|
||||
}
|
||||
|
||||
acquire(): (() => void) | undefined {
|
||||
this.reconcileActive();
|
||||
if (this.active) return undefined;
|
||||
this.admissions += 1;
|
||||
let released = false;
|
||||
@@ -33,17 +41,44 @@ export class MaintenanceBarrier {
|
||||
}
|
||||
|
||||
async activate(): Promise<void> {
|
||||
this.persistMarker();
|
||||
this.active = true;
|
||||
if (this.admissions === 0) return;
|
||||
await new Promise<void>((resolve) => this.waiters.push(resolve));
|
||||
let persistError: unknown;
|
||||
if (this.markerFile === undefined) {
|
||||
this.active = true;
|
||||
} else {
|
||||
try {
|
||||
this.persistMarker();
|
||||
} catch (error) {
|
||||
persistError = error;
|
||||
} finally {
|
||||
this.reconcileActive();
|
||||
}
|
||||
}
|
||||
if (!this.active) throw persistError;
|
||||
if (this.admissions > 0) {
|
||||
await new Promise<void>((resolve) => this.waiters.push(resolve));
|
||||
}
|
||||
if (persistError !== undefined) throw persistError;
|
||||
}
|
||||
|
||||
deactivate(): void {
|
||||
this.removeMarker();
|
||||
this.active = false;
|
||||
if (this.markerFile === undefined) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.removeMarker();
|
||||
} finally {
|
||||
this.reconcileActive();
|
||||
}
|
||||
}
|
||||
status(): { active: boolean; admissions: number } {
|
||||
this.reconcileActive();
|
||||
return { active: this.active, admissions: this.admissions };
|
||||
}
|
||||
|
||||
private reconcileActive(): void {
|
||||
if (this.markerFile !== undefined) this.active = existsSync(this.markerFile);
|
||||
}
|
||||
status(): { active: boolean; admissions: number } { return { active: this.active, admissions: this.admissions }; }
|
||||
|
||||
private persistMarker(): void {
|
||||
if (!this.markerFile) return;
|
||||
@@ -59,7 +94,7 @@ export class MaintenanceBarrier {
|
||||
}
|
||||
try {
|
||||
renameSync(temporary, this.markerFile);
|
||||
syncDirectory(directory);
|
||||
this.durability.syncDirectory(directory);
|
||||
} catch (error) {
|
||||
try { unlinkSync(temporary); } catch { /* already renamed or best-effort cleanup */ }
|
||||
throw error;
|
||||
@@ -69,12 +104,14 @@ export class MaintenanceBarrier {
|
||||
private removeMarker(): void {
|
||||
if (!this.markerFile || !existsSync(this.markerFile)) return;
|
||||
unlinkSync(this.markerFile);
|
||||
syncDirectory(dirname(this.markerFile));
|
||||
this.durability.syncDirectory(dirname(this.markerFile));
|
||||
}
|
||||
}
|
||||
|
||||
function syncDirectory(directory: string): void {
|
||||
if (process.platform === "win32") return;
|
||||
const fd = openSync(directory, "r");
|
||||
try { fsyncSync(fd); } finally { closeSync(fd); }
|
||||
}
|
||||
const defaultDurability: MaintenanceDurability = {
|
||||
syncDirectory(directory: string): void {
|
||||
if (process.platform === "win32") return;
|
||||
const fd = openSync(directory, "r");
|
||||
try { fsyncSync(fd); } finally { closeSync(fd); }
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user