SQlite Retry logic for database lock
parent
ac9ca4df39
commit
d779175b90
|
@ -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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue