SQlite Retry logic for database lock

master
j 2024-04-06 02:04:28 -04:00
parent ac9ca4df39
commit d779175b90
1 changed files with 52 additions and 16 deletions

View File

@ -42,14 +42,16 @@ export class DatabaseService {
* } * }
*/ */
public static async loadGameState(authorId: number): Promise<GameState | null> { public static async loadGameState(authorId: number): Promise<GameState | null> {
const db = await DatabaseService.getDatabase(); return this.withRetry(async () => {
const sql = `SELECT data FROM game_state WHERE authorId = ? AND active = TRUE`; const db = await DatabaseService.getDatabase();
const row = await db.get(sql, [authorId]); const sql = `SELECT data FROM game_state WHERE authorId = ? AND active = TRUE`;
if (row) { const row = await db.get(sql, [authorId]);
return JSON.parse(row.data) as GameState; if (row) {
} else { return JSON.parse(row.data) as GameState;
return null; } else {
} return null;
}
});
} }
/** /**
@ -63,15 +65,17 @@ export class DatabaseService {
* await DatabaseService.saveGameState(1, currentGameState); * await DatabaseService.saveGameState(1, currentGameState);
*/ */
public static async saveGameState(authorId: number, gameState: GameState): Promise<void> { public static async saveGameState(authorId: number, gameState: GameState): Promise<void> {
const db = await DatabaseService.getDatabase(); return this.withRetry(async () => {
const data = JSON.stringify(gameState); const db = await DatabaseService.getDatabase();
const sql = ` const data = JSON.stringify(gameState);
const sql = `
INSERT INTO game_state (authorId, data, active) VALUES (?, ?, TRUE) INSERT INTO game_state (authorId, data, active) VALUES (?, ?, TRUE)
ON CONFLICT(authorId) DO UPDATE SET ON CONFLICT(authorId) DO UPDATE SET
data = excluded.data data = excluded.data
WHERE active = TRUE; WHERE active = TRUE;
`; `;
await db.run(sql, [authorId, data]); await db.run(sql, [authorId, data]);
});
} }
/** /**
@ -84,9 +88,41 @@ export class DatabaseService {
* await DatabaseService.resetGameState(1); * await DatabaseService.resetGameState(1);
*/ */
public static async resetGameState(authorId: number): Promise<void> { public static async resetGameState(authorId: number): Promise<void> {
const db = await DatabaseService.getDatabase(); return this.withRetry(async () => {
const sql = `UPDATE game_state SET active = FALSE WHERE authorId = ? AND active = TRUE`; const db = await this.getDatabase();
await db.run(sql, [authorId]); const sql = `UPDATE game_state SET active = FALSE WHERE authorId = ? AND active = TRUE`;
await db.run(sql, [authorId]);
});
} }
/**
* Attempts to execute a database operation with retries on failure.
* This method is designed to handle SQLITE_BUSY errors by retrying the operation
* with an exponential backoff strategy. This is particularly useful for handling
* cases where the SQLite database is locked due to concurrent access attempts.
*
* @param operation A function that performs the database operation and returns a Promise.
* @param maxRetries The maximum number of retries before giving up. Defaults to 5.
* @param delay The initial delay (in milliseconds) before the first retry. This delay is doubled with each retry.
* @returns A Promise that resolves with the result of the operation, or rejects if the operation fails after all retries.
* @throws Will throw an error if the operation cannot be completed successfully within the allowed number of retries.
*/
private static async withRetry<T>(operation: () => Promise<T>, maxRetries: number = 5, delay: number = 100): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
console.log('Error', error)
// Type check or assertion to handle the error as an object with a 'code' property
const e = error as { code?: string };
if (e.code === 'SQLITE_BUSY') {
console.log(`Database is busy, retrying... Attempt ${i + 1}`);
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
} else {
throw error; // Rethrow if it's not an SQLITE_BUSY error
}
}
}
throw new Error('Max retries reached for database operation');
}
} }