Skip to content

Commit

Permalink
fix(core): add db retention to session registry
Browse files Browse the repository at this point in the history
  • Loading branch information
blakebyrnes committed Jul 6, 2023
1 parent 7cb291d commit 5af67e8
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 128 deletions.
65 changes: 49 additions & 16 deletions core/dbs/DefaultSessionRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import ISessionRegistry from '../interfaces/ISessionRegistry';
import SessionDb from './SessionDb';

export default class DefaultSessionRegistry implements ISessionRegistry {
private byId: { [sessionId: string]: SessionDb } = {};
private byId: {
[sessionId: string]: { db: SessionDb; deleteRequested?: boolean; connections: number };
} = {};

constructor(public defaultDir: string) {
if (!Fs.existsSync(this.defaultDir)) Fs.mkdirSync(this.defaultDir, { recursive: true });
Expand All @@ -16,7 +18,7 @@ export default class DefaultSessionRegistry implements ISessionRegistry {
public create(sessionId: string, customPath?: string): SessionDb {
const dbPath = this.resolvePath(sessionId, customPath);
const db = new SessionDb(sessionId, dbPath);
this.byId[sessionId] = db;
this.byId[sessionId] = { db, connections: 1 };
return db;
}

Expand All @@ -35,29 +37,60 @@ export default class DefaultSessionRegistry implements ISessionRegistry {
// eslint-disable-next-line @typescript-eslint/require-await
public async get(sessionId: string, customPath?: string): Promise<SessionDb> {
if (sessionId.endsWith('.db')) sessionId = sessionId.slice(0, -3);
if (!this.byId[sessionId]?.isOpen || this.byId[sessionId]?.isClosing) {
const entry = this.byId[sessionId];
if (!entry?.db?.isOpen || entry?.connections === 0) {
const dbPath = this.resolvePath(sessionId, customPath);
this.byId[sessionId] = new SessionDb(sessionId, dbPath, {
readonly: true,
fileMustExist: true,
});
this.byId[sessionId] = {
db: new SessionDb(sessionId, dbPath, {
readonly: true,
fileMustExist: true,
}),
connections: 1,
};
}
return this.byId[sessionId];
return this.byId[sessionId]?.db;
}

public async onClosed(sessionId: string, isDeleteRequested: boolean): Promise<void> {
public async retain(sessionId: string, customPath?: string): Promise<SessionDb> {
if (sessionId.endsWith('.db')) sessionId = sessionId.slice(0, -3);
const entry = this.byId[sessionId];
delete this.byId[sessionId];
if (entry && isDeleteRequested) {
try {
await Fs.promises.rm(entry.path);
} catch {}
if (!entry?.db?.isOpen) {
return this.get(sessionId, customPath);
}

if (entry) {
entry.connections += 1;
return entry.db;
}
}

public async close(sessionId: string, isDeleteRequested: boolean): Promise<void> {
const entry = this.byId[sessionId];
if (!entry) return;
entry.connections -= 1;
entry.deleteRequested ||= isDeleteRequested;

if (entry.connections < 1) {
delete this.byId[sessionId];
entry.db.close();
if (entry.deleteRequested) {
try {
await Fs.promises.rm(entry.db.path);
} catch {}
}
} else if (!entry.db?.readonly) {
entry.db.recycle();
}
}

public shutdown(): Promise<void> {
public async shutdown(): Promise<void> {
for (const [key, value] of Object.entries(this.byId)) {
value.close();
value.db.close();
if (value.deleteRequested) {
try {
await Fs.promises.rm(value.db.path);
} catch {}
}
delete this.byId[key];
}
return Promise.resolve();
Expand Down
181 changes: 96 additions & 85 deletions core/dbs/SessionDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,34 +48,33 @@ export default class SessionDb {

public readonly path: string;

public readonly commands: CommandsTable;
public readonly frames: FramesTable;
public readonly frameNavigations: FrameNavigationsTable;
public readonly sockets: SocketsTable;
public readonly resources: ResourcesTable;
public readonly resourceStates: ResourceStatesTable;
public readonly websocketMessages: WebsocketMessagesTable;
public readonly domChanges: DomChangesTable;
public readonly detachedElements: DetachedElementsTable;
public readonly detachedResources: DetachedResourcesTable;
public readonly snippets: SnippetsTable;
public readonly interactions: InteractionStepsTable;
public readonly flowHandlers: FlowHandlersTable;
public readonly flowCommands: FlowCommandsTable;
public readonly pageLogs: PageLogsTable;
public readonly sessionLogs: SessionLogsTable;
public readonly session: SessionTable;
public readonly mouseEvents: MouseEventsTable;
public readonly focusEvents: FocusEventsTable;
public readonly scrollEvents: ScrollEventsTable;
public readonly storageChanges: StorageChangesTable;
public readonly screenshots: ScreenshotsTable;
public readonly devtoolsMessages: DevtoolsMessagesTable;
public readonly awaitedEvents: AwaitedEventsTable;
public readonly tabs: TabsTable;
public readonly output: OutputTable;
public commands: CommandsTable;
public frames: FramesTable;
public frameNavigations: FrameNavigationsTable;
public sockets: SocketsTable;
public resources: ResourcesTable;
public resourceStates: ResourceStatesTable;
public websocketMessages: WebsocketMessagesTable;
public domChanges: DomChangesTable;
public detachedElements: DetachedElementsTable;
public detachedResources: DetachedResourcesTable;
public snippets: SnippetsTable;
public interactions: InteractionStepsTable;
public flowHandlers: FlowHandlersTable;
public flowCommands: FlowCommandsTable;
public pageLogs: PageLogsTable;
public sessionLogs: SessionLogsTable;
public session: SessionTable;
public mouseEvents: MouseEventsTable;
public focusEvents: FocusEventsTable;
public scrollEvents: ScrollEventsTable;
public storageChanges: StorageChangesTable;
public screenshots: ScreenshotsTable;
public devtoolsMessages: DevtoolsMessagesTable;
public awaitedEvents: AwaitedEventsTable;
public tabs: TabsTable;
public output: OutputTable;
public readonly sessionId: string;
public isClosing = false;

public keepAlive = false;

Expand All @@ -98,61 +97,7 @@ export default class SessionDb {
this.saveInterval = setInterval(this.flush.bind(this), 5e3).unref();
}

this.commands = new CommandsTable(this.db);
this.tabs = new TabsTable(this.db);
this.frames = new FramesTable(this.db);
this.frameNavigations = new FrameNavigationsTable(this.db);
this.sockets = new SocketsTable(this.db);
this.resources = new ResourcesTable(this.db);
this.resourceStates = new ResourceStatesTable(this.db);
this.websocketMessages = new WebsocketMessagesTable(this.db);
this.domChanges = new DomChangesTable(this.db);
this.detachedElements = new DetachedElementsTable(this.db);
this.detachedResources = new DetachedResourcesTable(this.db);
this.snippets = new SnippetsTable(this.db);
this.flowHandlers = new FlowHandlersTable(this.db);
this.flowCommands = new FlowCommandsTable(this.db);
this.pageLogs = new PageLogsTable(this.db);
this.session = new SessionTable(this.db);
this.interactions = new InteractionStepsTable(this.db);
this.mouseEvents = new MouseEventsTable(this.db);
this.focusEvents = new FocusEventsTable(this.db);
this.scrollEvents = new ScrollEventsTable(this.db);
this.sessionLogs = new SessionLogsTable(this.db);
this.screenshots = new ScreenshotsTable(this.db);
this.storageChanges = new StorageChangesTable(this.db);
this.devtoolsMessages = new DevtoolsMessagesTable(this.db);
this.awaitedEvents = new AwaitedEventsTable(this.db);
this.output = new OutputTable(this.db);

this.tables.push(
this.commands,
this.tabs,
this.frames,
this.frameNavigations,
this.sockets,
this.resources,
this.resourceStates,
this.websocketMessages,
this.domChanges,
this.detachedElements,
this.detachedResources,
this.snippets,
this.flowHandlers,
this.flowCommands,
this.pageLogs,
this.session,
this.interactions,
this.mouseEvents,
this.focusEvents,
this.scrollEvents,
this.sessionLogs,
this.devtoolsMessages,
this.screenshots,
this.storageChanges,
this.awaitedEvents,
this.output,
);
this.attach();

if (!readonly) {
this.batchInsert = this.db.transaction(() => {
Expand Down Expand Up @@ -201,18 +146,26 @@ export default class SessionDb {
this.flush();
}

if (env.enableSqliteWal && !this.db.readonly) {
this.db.pragma('journal_mode = DELETE');
}

if (this.keepAlive) {
this.db.readonly = true;
return;
}

if (env.enableSqliteWal && !this.db.readonly) {
this.db.pragma('journal_mode = DELETE');
}
this.db.close();
this.db = null;
}

public recycle(): void {
this.close();

this.db = new Database(this.path, { readonly: true });
this.attach();
}

public flush(): void {
if (this.batchInsert) {
try {
Expand All @@ -228,4 +181,62 @@ export default class SessionDb {
}
}
}

private attach(): void {
this.commands = new CommandsTable(this.db);
this.tabs = new TabsTable(this.db);
this.frames = new FramesTable(this.db);
this.frameNavigations = new FrameNavigationsTable(this.db);
this.sockets = new SocketsTable(this.db);
this.resources = new ResourcesTable(this.db);
this.resourceStates = new ResourceStatesTable(this.db);
this.websocketMessages = new WebsocketMessagesTable(this.db);
this.domChanges = new DomChangesTable(this.db);
this.detachedElements = new DetachedElementsTable(this.db);
this.detachedResources = new DetachedResourcesTable(this.db);
this.snippets = new SnippetsTable(this.db);
this.flowHandlers = new FlowHandlersTable(this.db);
this.flowCommands = new FlowCommandsTable(this.db);
this.pageLogs = new PageLogsTable(this.db);
this.session = new SessionTable(this.db);
this.interactions = new InteractionStepsTable(this.db);
this.mouseEvents = new MouseEventsTable(this.db);
this.focusEvents = new FocusEventsTable(this.db);
this.scrollEvents = new ScrollEventsTable(this.db);
this.sessionLogs = new SessionLogsTable(this.db);
this.screenshots = new ScreenshotsTable(this.db);
this.storageChanges = new StorageChangesTable(this.db);
this.devtoolsMessages = new DevtoolsMessagesTable(this.db);
this.awaitedEvents = new AwaitedEventsTable(this.db);
this.output = new OutputTable(this.db);

this.tables.push(
this.commands,
this.tabs,
this.frames,
this.frameNavigations,
this.sockets,
this.resources,
this.resourceStates,
this.websocketMessages,
this.domChanges,
this.detachedElements,
this.detachedResources,
this.snippets,
this.flowHandlers,
this.flowCommands,
this.pageLogs,
this.session,
this.interactions,
this.mouseEvents,
this.focusEvents,
this.scrollEvents,
this.sessionLogs,
this.devtoolsMessages,
this.screenshots,
this.storageChanges,
this.awaitedEvents,
this.output,
);
}
}
3 changes: 2 additions & 1 deletion core/interfaces/ISessionRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import SessionDb from '../dbs/SessionDb';
export default interface ISessionRegistry {
defaultDir: string;
ids(): Promise<string[]>;
retain(sessionId: string, customPath?: string): Promise<SessionDb>;
get(sessionId: string, customPath?: string): Promise<SessionDb>;
create(sessionId: string, customPath?: string): SessionDb;
onClosed(sessionId: string, isDeleteRequested: boolean): Promise<void>;
close(sessionId: string, isDeleteRequested: boolean): Promise<void>;
shutdown(): Promise<void>
}
8 changes: 1 addition & 7 deletions core/lib/Session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,6 @@ export default class Session
const customPath = this.getCustomSessionPath(fromSessionId);
db = await this.sessionRegistry.get(fromSessionId, customPath);
}
db.flush();
return DetachedAssets.getElements(db, name);
}

Expand Down Expand Up @@ -450,7 +449,6 @@ export default class Session
});

const closedEvent = { waitForPromise: null };
this.db.isClosing = true;
try {
this.emit('closed', closedEvent);
await closedEvent.waitForPromise;
Expand All @@ -477,11 +475,7 @@ export default class Session
this.removeAllListeners();

try {
this.db.close();
} catch {}

try {
await sessionRegistry.onClosed(this.id, this.options.sessionPersistence === false);
await sessionRegistry.close(this.id, this.options.sessionPersistence === false);
} catch (e) {
/* no-op */
}
Expand Down
Loading

0 comments on commit 5af67e8

Please sign in to comment.