| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- #!/usr/bin/env node
- // To run in dev:
- //
- // docker compose run --rm project-history scripts/clear_deleted.js
- //
- // In production:
- //
- // docker run --rm $(docker ps -lq) scripts/clear_deleted.js
- import async from 'async'
- import Settings from '@overleaf/settings'
- import redis from '@overleaf/redis-wrapper'
- import { db, ObjectId } from '../app/js/mongodb.js'
- import * as SyncManager from '../app/js/SyncManager.js'
- import * as UpdatesProcessor from '../app/js/UpdatesProcessor.js'
- const rclient = redis.createClient(Settings.redis.project_history)
- const Keys = Settings.redis.project_history.key_schema
- const argv = process.argv.slice(2)
- const limit = parseInt(argv[0], 10) || null
- const force = argv[1] === 'force' || false
- let projectNotFoundErrors = 0
- let projectImportedFromV1Errors = 0
- const projectsNotFound = []
- const projectsImportedFromV1 = []
- let projectNoHistoryIdErrors = 0
- let projectsFailedErrors = 0
- const projectsFailed = []
- let projectsBrokenSyncErrors = 0
- const projectsBrokenSync = []
- function checkAndClear(project, callback) {
- const projectId = project.project_id
- console.log('checking project', projectId)
- // These can probably also be reset and their overleaf.history.id unset
- // (unless they are v1 projects).
- function checkNotV1Project(cb) {
- db.projects.findOne(
- { _id: new ObjectId(projectId) },
- { projection: { overleaf: true } },
- (err, result) => {
- console.log(
- '1. looking in mongo projects collection: err',
- err,
- 'result',
- JSON.stringify(result)
- )
- if (err) {
- return cb(err)
- }
- if (!result) {
- return cb(new Error('project not found in mongo'))
- }
- if (result && result.overleaf && !result.overleaf.id) {
- if (result.overleaf.history.id) {
- console.log(
- ' - project is not imported from v1 and has a history id - ok to resync'
- )
- return cb()
- } else {
- console.log(
- ' - project is not imported from v1 but does not have a history id'
- )
- return cb(new Error('no history id'))
- }
- } else {
- cb(new Error('project is imported from v1 - will not resync it'))
- }
- }
- )
- }
- function startResync(cb) {
- if (force) {
- console.log('2. starting resync for', projectId)
- SyncManager.startResync(projectId, err => {
- if (err) {
- console.log('ERR', JSON.stringify(err.message))
- return cb(err)
- }
- setTimeout(cb, 3000) // include a delay to allow the request to be processed
- })
- } else {
- console.log('2. dry run, would start resync for', projectId)
- cb()
- }
- }
- function forceFlush(cb) {
- if (force) {
- console.log('3. forcing a flush for', projectId)
- UpdatesProcessor.processUpdatesForProject(projectId, err => {
- console.log('err', err)
- return cb(err)
- })
- } else {
- console.log('3. dry run, would force a flush for', projectId)
- cb()
- }
- }
- function watchRedisQueue(cb) {
- const key = Keys.projectHistoryOps({ project_id: projectId })
- function checkQueueEmpty(_callback) {
- rclient.llen(key, (err, result) => {
- console.log('LLEN', projectId, err, result)
- if (err) {
- _callback(err)
- }
- if (result === 0) {
- _callback()
- } else {
- _callback(new Error('queue not empty'))
- }
- })
- }
- if (force) {
- console.log('4. checking redis queue key', key)
- async.retry({ times: 30, interval: 1000 }, checkQueueEmpty, err => {
- cb(err)
- })
- } else {
- console.log('4. dry run, would check redis key', key)
- cb()
- }
- }
- function checkMongoFailureEntry(cb) {
- if (force) {
- console.log('5. checking key in mongo projectHistoryFailures', projectId)
- db.projectHistoryFailures.findOne(
- { project_id: projectId },
- { projection: { _id: 1 } },
- (err, result) => {
- console.log('got result', err, result)
- if (err) {
- return cb(err)
- }
- if (result) {
- return cb(new Error('failure record still exists'))
- }
- return cb()
- }
- )
- } else {
- console.log('5. would check failure record for', projectId, 'in mongo')
- cb()
- }
- }
- // do the checks and deletions
- async.waterfall(
- [
- checkNotV1Project,
- startResync,
- forceFlush,
- watchRedisQueue,
- checkMongoFailureEntry,
- ],
- err => {
- if (!err) {
- return setTimeout(callback, 1000) // include a 1 second delay
- } else if (err.message === 'project not found in mongo') {
- projectNotFoundErrors++
- projectsNotFound.push(projectId)
- return callback()
- } else if (err.message === 'no history id') {
- projectNoHistoryIdErrors++
- return callback()
- } else if (
- err.message === 'project is imported from v1 - will not resync it'
- ) {
- projectImportedFromV1Errors++
- projectsImportedFromV1.push(projectId)
- return callback()
- } else if (
- err.message === 'history store a non-success status code: 422'
- ) {
- projectsFailedErrors++
- projectsFailed.push(projectId)
- return callback()
- } else if (err.message === 'sync ongoing') {
- projectsBrokenSyncErrors++
- projectsBrokenSync.push(projectId)
- return callback()
- } else {
- console.log('error:', err)
- return callback()
- }
- }
- )
- }
- // find all the broken projects from the failure records
- const errorsToResync = [
- 'Error: history store a non-success status code: 422',
- 'OError: history store a non-success status code: 422',
- 'OpsOutOfOrderError: project structure version out of order',
- ]
- async function main() {
- const results = await db.projectHistoryFailures
- .find({ error: { $in: errorsToResync } })
- .toArray()
- console.log('number of queues without history store 442 =', results.length)
- // now check if the project is truly deleted in mongo
- async.eachSeries(results.slice(0, limit), checkAndClear, err => {
- console.log('Final error status', err)
- console.log(
- 'Project flush failed again errors',
- projectsFailedErrors,
- projectsFailed
- )
- console.log(
- 'Project flush ongoing errors',
- projectsBrokenSyncErrors,
- projectsBrokenSync
- )
- console.log(
- 'Project not found errors',
- projectNotFoundErrors,
- projectsNotFound
- )
- console.log('Project without history_id errors', projectNoHistoryIdErrors)
- console.log(
- 'Project imported from V1 errors',
- projectImportedFromV1Errors,
- projectsImportedFromV1
- )
- process.exit()
- })
- }
- main().catch(error => {
- console.error(error)
- process.exit(1)
- })
|