diff --git a/services/history-v1/api/controllers/project_import.js b/services/history-v1/api/controllers/project_import.js index 99f56cb7e4..638873d105 100644 --- a/services/history-v1/api/controllers/project_import.js +++ b/services/history-v1/api/controllers/project_import.js @@ -163,6 +163,7 @@ async function flushChanges(req, res, next) { maxChanges: 0, minChangeTimestamp: farFuture, maxChangeTimestamp: farFuture, + autoResync: true, } try { await persistBuffer(projectId, limits) diff --git a/services/history-v1/config/custom-environment-variables.json b/services/history-v1/config/custom-environment-variables.json index f0827fc538..686ca25407 100644 --- a/services/history-v1/config/custom-environment-variables.json +++ b/services/history-v1/config/custom-environment-variables.json @@ -104,5 +104,9 @@ "password": "REDIS_PASSWORD", "port": "REDIS_PORT" } + }, + "projectHistory": { + "host": "PROJECT_HISTORY_HOST", + "port": "PROJECT_HISTORY_PORT" } } diff --git a/services/history-v1/config/default.json b/services/history-v1/config/default.json index 5222b84d87..e7732fe3f7 100644 --- a/services/history-v1/config/default.json +++ b/services/history-v1/config/default.json @@ -39,5 +39,8 @@ "databasePoolMin": "2", "databasePoolMax": "10", "httpsOnly": "false", - "httpRequestTimeout": "300000" + "httpRequestTimeout": "300000", + "projectHistory": { + "port": "3054" + } } diff --git a/services/history-v1/storage/lib/persist_buffer.js b/services/history-v1/storage/lib/persist_buffer.js index 0cf1a81bd8..578142135c 100644 --- a/services/history-v1/storage/lib/persist_buffer.js +++ b/services/history-v1/storage/lib/persist_buffer.js @@ -9,6 +9,7 @@ const chunkStore = require('./chunk_store') const { BlobStore } = require('./blob_store') const BatchBlobStore = require('./batch_blob_store') const persistChanges = require('./persist_changes') +const resyncProject = require('./resync_project') const redisBackend = require('./chunk_store/redis') /** @@ -171,6 +172,24 @@ async function persistBuffer(projectId, limits) { 'updated persisted version in Redis' ) + // 7. Resync the project if content hash validation failed + if (limits.autoResync && persistResult.resyncNeeded) { + if ( + changesToPersist.some( + change => change.getOrigin()?.getKind() === 'history-resync' + ) + ) { + // To avoid an infinite loop, do not resync if the current batch of + // changes contains a history resync. + logger.warn( + { projectId }, + 'content hash validation failed while persisting a history resync, skipping additional resync' + ) + } else { + await resyncProject(projectId) + } + } + logger.debug( { projectId, finalPersistedVersion: newEndVersion }, 'persistBuffer operation completed successfully' diff --git a/services/history-v1/storage/lib/resync_project.js b/services/history-v1/storage/lib/resync_project.js new file mode 100644 index 0000000000..3ec680bb5b --- /dev/null +++ b/services/history-v1/storage/lib/resync_project.js @@ -0,0 +1,14 @@ +// @ts-check + +const config = require('config') +const { fetchNothing } = require('@overleaf/fetch-utils') + +const PROJECT_HISTORY_URL = `http://${config.projectHistory.host}:${config.projectHistory.port}` + +async function resyncProject(projectId) { + await fetchNothing(`${PROJECT_HISTORY_URL}/project/${projectId}/resync`, { + method: 'POST', + }) +} + +module.exports = resyncProject diff --git a/services/history-v1/storage/scripts/persist_redis_chunks.mjs b/services/history-v1/storage/scripts/persist_redis_chunks.mjs index 03381ac63a..dd7e9f3a51 100644 --- a/services/history-v1/storage/scripts/persist_redis_chunks.mjs +++ b/services/history-v1/storage/scripts/persist_redis_chunks.mjs @@ -45,6 +45,7 @@ async function persistProjectAction(projectId) { maxChanges: 0, minChangeTimestamp: farFuture, maxChangeTimestamp: farFuture, + autoResync: true, } await persistBuffer(projectId, limits) if (job && job.close) {