From 4055758e69a7c93b78a135cfb28878e4debff6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Iv=C3=A1n=20Vieitez=20Parra?= <3857362+corrideat@users.noreply.github.com> Date: Thu, 9 Jan 2025 16:02:43 +0000 Subject: [PATCH] Use persistent actions --- backend/routes.js | 4 +-- backend/server.js | 11 ++++-- shared/domains/chelonia/chelonia.js | 2 +- shared/domains/chelonia/persistent-actions.js | 36 +++++++++++-------- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/backend/routes.js b/backend/routes.js index a4594f58d2..7263773625 100644 --- a/backend/routes.js +++ b/backend/routes.js @@ -621,8 +621,8 @@ route.POST('/deleteContract/{hash}', { // Authentication passed, now proceed to delete the contract and its associated // keys try { - await sbp('backend/deleteContract', hash) - return h.response() + const [id] = sbp('chelonia.persistentActions/enqueue', ['backend/deleteContract', hash]) + return h.response({ id }).code(202) } catch (e) { switch (e.name) { case 'BackendErrorNotFound': diff --git a/backend/server.js b/backend/server.js index cdbbe58295..88750a33a3 100644 --- a/backend/server.js +++ b/backend/server.js @@ -5,6 +5,7 @@ import sbp from '@sbp/sbp' import chalk from 'chalk' import { GIMessage } from '~/shared/domains/chelonia/GIMessage.js' import '~/shared/domains/chelonia/chelonia.js' +import '~/shared/domains/chelonia/persistent-actions.js' import { SERVER } from '~/shared/domains/chelonia/presets.js' import type { SubMessage, UnsubMessage } from '~/shared/pubsub.js' import { appendToIndexFactory, initDB, removeFromIndexFactory } from './database.js' @@ -271,9 +272,9 @@ sbp('sbp/selectors/register', { const resource = Buffer.from(await sbp('chelonia/db/get', resourceCid)).toString() if (resource) { if (resource.includes('previousHEAD') && resource.includes('contractID') && resource.includes('op') && resource.includes('height')) { - return sbp('backend/deleteContract', resourceCid) + return sbp('chelonia.persistentActions/enqueue', ['backend/deleteContract', resourceCid]) } else { - return sbp('backend/deleteFile', resourceCid) + return sbp('chelonia.persistentActions/enqueue', ['backend/deleteFile', resourceCid]) } } })) @@ -428,6 +429,9 @@ sbp('okTurtles.data/set', PUBSUB_INSTANCE, createServer(hapi.listener, { ;(async function () { await initDB() await sbp('chelonia/configure', SERVER) + sbp('chelonia.persistentActions/configure', { + databaseKey: '_private_persistent_actions' + }) // Load the saved Chelonia state // First, get the contract index const savedStateIndex = await sbp('chelonia/db/get', '_private_cheloniaState_index') @@ -469,6 +473,9 @@ sbp('okTurtles.data/set', PUBSUB_INSTANCE, createServer(hapi.listener, { }) })) } + sbp('chelonia.persistentActions/load').catch(e => { + console.error(e, 'Error loading persistent actions') + }) // https://hapi.dev/tutorials/plugins await hapi.register([ { plugin: require('./auth.js') }, diff --git a/shared/domains/chelonia/chelonia.js b/shared/domains/chelonia/chelonia.js index 81d2363ce7..7f52bfabc9 100644 --- a/shared/domains/chelonia/chelonia.js +++ b/shared/domains/chelonia/chelonia.js @@ -627,7 +627,7 @@ export default (sbp('sbp/selectors/register', { }) }] case NOTIFICATION_TYPE.DELETION: - return [k, (msg) => v(msg.data)] + return [k, (msg) => (v: Function)(msg.data)] default: return [k, v] } diff --git a/shared/domains/chelonia/persistent-actions.js b/shared/domains/chelonia/persistent-actions.js index 946f387870..db495bd1b0 100644 --- a/shared/domains/chelonia/persistent-actions.js +++ b/shared/domains/chelonia/persistent-actions.js @@ -103,7 +103,11 @@ class PersistentAction { // Schedule a retry if appropriate. if (status.nextRetry) { // Note: there should be no older active timeout to clear. - this.timer = setTimeout(() => this.attempt(), this.options.retrySeconds * 1e3) + this.timer = setTimeout(() => { + this.attempt().catch((e) => { + console.error('Error attempting persistent action', id, e) + }) + }, this.options.retrySeconds * 1e3) } } @@ -142,12 +146,11 @@ sbp('sbp/selectors/register', { // Cancels a specific action by its ID. // The action won't be retried again, but an async action cannot be aborted if its promise is stil attempting. - 'chelonia.persistentActions/cancel' (id: UUIDV4): void { + async 'chelonia.persistentActions/cancel' (id: UUIDV4): Promise { if (id in this.actionsByID) { this.actionsByID[id].cancel() delete this.actionsByID[id] - // Likely no need to await this call. - sbp('chelonia.persistentActions/save') + return await sbp('chelonia.persistentActions/save') } }, @@ -172,9 +175,14 @@ sbp('sbp/selectors/register', { this.actionsByID[action.id] = action ids.push(action.id) } - // Likely no need to await this call. - sbp('chelonia.persistentActions/save') - for (const id of ids) this.actionsByID[id].attempt() + sbp('chelonia.persistentActions/save').catch(e => { + console.error('Error saving persistent actions', e) + }) + for (const id of ids) { + this.actionsByID[id].attempt().catch((e) => { + console.error('Error attempting persistent action', id, e) + }) + } return ids }, @@ -182,9 +190,9 @@ sbp('sbp/selectors/register', { // - 'status.failedAttemptsSoFar' will still be increased upon failure. // - Does nothing if a retry is already running. // - Does nothing if the action has already been resolved, rejected or cancelled. - 'chelonia.persistentActions/forceRetry' (id: UUIDV4): void { + 'chelonia.persistentActions/forceRetry' (id: UUIDV4): void | Promise { if (id in this.actionsByID) { - this.actionsByID[id].attempt() + return this.actionsByID[id].attempt() } }, @@ -198,16 +206,16 @@ sbp('sbp/selectors/register', { // TODO: find a cleaner alternative. this.actionsByID[id].id = id } - sbp('chelonia.persistentActions/retryAll') + return sbp('chelonia.persistentActions/retryAll') }, // Retry all existing persisted actions. // TODO: add some delay between actions so as not to spam the server, // or have a way to issue them all at once in a single network call. - 'chelonia.persistentActions/retryAll' (): void { - for (const id in this.actionsByID) { - sbp('chelonia.persistentActions/forceRetry', id) - } + 'chelonia.persistentActions/retryAll' () { + return Promise.allSettled( + Object.keys(this.actionsByID).map(id => sbp('chelonia.persistentActions/forceRetry', id)) + ) }, // Updates the database version of the attempting action list.