mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-14 11:53:49 +00:00
refactor: debounce writing to disk
This commit is contained in:
committed by
Carlos Valente
parent
e16ec3f388
commit
77779a4648
@@ -108,11 +108,6 @@ export function createTransaction(options: TransactionOptions): Transaction {
|
||||
function commit(shouldProcess: boolean = true) {
|
||||
// if the rundown is mutable we persist the changes
|
||||
if (options.mutableRundown) {
|
||||
// schedule a database update
|
||||
setImmediate(async () => {
|
||||
await getDataProvider().setRundown(cachedRundown.id, cachedRundown);
|
||||
});
|
||||
|
||||
// update fields which are agnostic of whether the rundown is processed
|
||||
cachedRundown.revision = cachedRundown.revision + 1;
|
||||
cachedRundown.title = rundown.title;
|
||||
@@ -133,16 +128,17 @@ export function createTransaction(options: TransactionOptions): Transaction {
|
||||
cachedRundown.flatOrder = metadata.flatEntryOrder;
|
||||
rundownMetadata = metadata;
|
||||
}
|
||||
|
||||
// persist after all mutations are applied
|
||||
getDataProvider().setRundown(cachedRundown.id, cachedRundown);
|
||||
}
|
||||
|
||||
// if the customFields are mutable we persist the changes
|
||||
if (options.mutableCustomFields) {
|
||||
// schedule a database update
|
||||
setImmediate(async () => {
|
||||
await getDataProvider().setCustomFields(projectCustomFields);
|
||||
});
|
||||
|
||||
projectCustomFields = customFields;
|
||||
|
||||
// persist after reassignment
|
||||
getDataProvider().setCustomFields(projectCustomFields);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -552,10 +548,8 @@ export const rundownMutation = {
|
||||
/**
|
||||
* Exposes a way to update a rundown which is not active
|
||||
*/
|
||||
export function updateBackgroundRundown(rundownId: string, rundown: Rundown) {
|
||||
setImmediate(async () => {
|
||||
await getDataProvider().setRundown(rundownId, rundown);
|
||||
});
|
||||
export async function updateBackgroundRundown(rundownId: string, rundown: Rundown) {
|
||||
await getDataProvider().setRundown(rundownId, rundown);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -652,9 +646,7 @@ export function init(initialRundown: Readonly<Rundown>, initialCustomFields: Rea
|
||||
rundownMetadata = metadata;
|
||||
|
||||
// defer writing to the database
|
||||
setImmediate(async () => {
|
||||
await getDataProvider().setRundown(cachedRundown.id, cachedRundown);
|
||||
});
|
||||
getDataProvider().setRundown(cachedRundown.id, cachedRundown);
|
||||
|
||||
return { rundown, rundownMetadata, customFields, revision: rundown.revision };
|
||||
}
|
||||
|
||||
@@ -486,7 +486,7 @@ export async function editCustomField(
|
||||
if (rundownId !== rundown.id) {
|
||||
const backgroundRundown = structuredClone(projectRundowns[rundownId]);
|
||||
customFieldMutation.renameUsages(backgroundRundown, oldKey, newKey);
|
||||
updateBackgroundRundown(rundownId, backgroundRundown);
|
||||
await updateBackgroundRundown(rundownId, backgroundRundown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ export async function deleteCustomField(key: CustomFieldKey, projectRundowns: Pr
|
||||
if (rundownId !== rundown.id) {
|
||||
const backgroundRundown = structuredClone(projectRundowns[rundownId]);
|
||||
customFieldMutation.removeUsages(backgroundRundown, key);
|
||||
updateBackgroundRundown(rundownId, backgroundRundown);
|
||||
await updateBackgroundRundown(rundownId, backgroundRundown);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { integrationRouter } from './api-integration/integration.router.js';
|
||||
|
||||
// Import adapters
|
||||
import { socket } from './adapters/WebsocketAdapter.js';
|
||||
import { getDataProvider } from './classes/data-provider/DataProvider.js';
|
||||
import { getDataProvider, flushPendingWrites } from './classes/data-provider/DataProvider.js';
|
||||
|
||||
// Services
|
||||
import { logger } from './classes/Logger.js';
|
||||
@@ -266,6 +266,10 @@ export const startIntegrations = async () => {
|
||||
export const shutdown = async (exitCode = 0) => {
|
||||
consoleHighlight(`Ontime shutting down with code ${exitCode}`);
|
||||
|
||||
await flushPendingWrites().catch((_error) => {
|
||||
/** nothing do to here */
|
||||
});
|
||||
|
||||
// clear the restore file if it was a normal exit
|
||||
// 0 means it was a SIGNAL
|
||||
// 1 means crash -> keep the file
|
||||
|
||||
@@ -180,10 +180,58 @@ async function mergeIntoData(newData: Partial<DatabaseModel>): ReadonlyPromise<D
|
||||
return db.data;
|
||||
}
|
||||
|
||||
// Module-level state for debounced writes
|
||||
let pendingWrite: NodeJS.Timeout | null = null;
|
||||
let activeWrite: Promise<void> | null = null;
|
||||
const writeDelayMs = 3000; // 3 seconds
|
||||
|
||||
/**
|
||||
* Handles persisting data to file
|
||||
* Handles persisting data to file with trailing-edge debounce
|
||||
* Multiple rapid calls will be coalesced into a single write
|
||||
*/
|
||||
async function persist() {
|
||||
if (isTest) return;
|
||||
|
||||
// Cancel any pending write and reschedule
|
||||
if (pendingWrite) {
|
||||
clearTimeout(pendingWrite);
|
||||
}
|
||||
|
||||
// Schedule new write after quiet period
|
||||
pendingWrite = setTimeout(async () => {
|
||||
pendingWrite = null;
|
||||
|
||||
// Wait for any in-progress write to finish first
|
||||
if (activeWrite) {
|
||||
await activeWrite;
|
||||
}
|
||||
|
||||
try {
|
||||
activeWrite = db.write();
|
||||
await activeWrite;
|
||||
} catch (error) {
|
||||
console.error('Failed to persist database:', error);
|
||||
} finally {
|
||||
activeWrite = null;
|
||||
}
|
||||
}, writeDelayMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force immediate write of any pending changes
|
||||
*/
|
||||
export async function flushPendingWrites() {
|
||||
if (isTest) return;
|
||||
|
||||
if (pendingWrite) {
|
||||
clearTimeout(pendingWrite);
|
||||
pendingWrite = null;
|
||||
}
|
||||
|
||||
// Wait for any in-progress write to finish
|
||||
if (activeWrite) {
|
||||
await activeWrite;
|
||||
}
|
||||
|
||||
await db.write();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user