| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- // TODO: This file was created by bulk-decaffeinate.
- // Fix any style issues and re-enable lint.
- /*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
- import async from 'async'
- import logger from '@overleaf/logger'
- import OError from '@overleaf/o-error'
- import metrics from '@overleaf/metrics'
- import Settings from '@overleaf/settings'
- import _ from 'lodash'
- import * as RedisManager from './RedisManager.js'
- import * as UpdatesProcessor from './UpdatesProcessor.js'
- import * as ErrorRecorder from './ErrorRecorder.js'
- export function flushIfOld(projectId, cutoffTime, callback) {
- if (callback == null) {
- callback = function () {}
- }
- return RedisManager.getFirstOpTimestamp(
- projectId,
- function (err, firstOpTimestamp) {
- if (err != null) {
- return callback(OError.tag(err))
- }
- // In the normal case, the flush marker will be set with the
- // timestamp of the oldest operation in the queue by docupdater.
- // If the marker is not set for any reason, we flush it anyway
- // for safety.
- if (!firstOpTimestamp || firstOpTimestamp < cutoffTime) {
- logger.debug(
- { projectId, firstOpTimestamp, cutoffTime },
- 'flushing old project'
- )
- metrics.inc('flush-old-updates', 1, { status: 'flushed' })
- return UpdatesProcessor.processUpdatesForProject(projectId, callback)
- } else if (Settings.shortHistoryQueues.includes(projectId)) {
- logger.debug(
- { projectId, firstOpTimestamp, cutoffTime },
- 'flushing project with short queue'
- )
- metrics.inc('flush-old-updates', 1, { status: 'short-queue' })
- return UpdatesProcessor.processUpdatesForProject(projectId, callback)
- } else {
- metrics.inc('flush-old-updates', 1, { status: 'skipped' })
- return callback()
- }
- }
- )
- }
- export function flushOldOps(options, callback) {
- if (callback == null) {
- callback = function () {}
- }
- logger.debug({ options }, 'starting flush of old ops')
- // allow running flush in background for cron jobs
- if (options.background) {
- // return immediate response to client, then discard callback
- callback(null, { message: 'running flush in background' })
- callback = function () {}
- }
- return RedisManager.getProjectIdsWithHistoryOps(
- null,
- function (error, projectIds) {
- if (error != null) {
- return callback(OError.tag(error))
- }
- return ErrorRecorder.getFailedProjects(
- function (error, projectHistoryFailures) {
- if (error != null) {
- return callback(OError.tag(error))
- }
- // exclude failed projects already in projectHistoryFailures
- const failedProjects = new Set()
- for (const entry of Array.from(projectHistoryFailures)) {
- failedProjects.add(entry.project_id)
- }
- // randomise order so we get different projects if there is a limit
- projectIds = _.shuffle(projectIds)
- const maxAge = options.maxAge || 6 * 3600 // default to 6 hours
- const cutoffTime = new Date(Date.now() - maxAge * 1000)
- const startTime = new Date()
- let count = 0
- const jobs = projectIds.map(
- projectId =>
- function (cb) {
- const timeTaken = new Date() - startTime
- count++
- if (
- (options != null ? options.timeout : undefined) &&
- timeTaken > options.timeout
- ) {
- // finish early due to timeout, return an error to bail out of the async iteration
- logger.debug('background retries timed out')
- return cb(new OError('retries timed out'))
- }
- if (
- (options != null ? options.limit : undefined) &&
- count > options.limit
- ) {
- // finish early due to reaching limit, return an error to bail out of the async iteration
- logger.debug({ count }, 'background retries hit limit')
- return cb(new OError('hit limit'))
- }
- if (failedProjects.has(projectId)) {
- // skip failed projects
- return setTimeout(cb, options.queueDelay || 100) // pause between flushes
- }
- return flushIfOld(projectId, cutoffTime, function (err) {
- if (err != null) {
- logger.warn(
- { projectId, err },
- 'error flushing old project'
- )
- }
- return setTimeout(cb, options.queueDelay || 100)
- })
- }
- ) // pause between flushes
- return async.series(
- async.reflectAll(jobs),
- function (error, results) {
- const success = []
- const failure = []
- results.forEach((result, i) => {
- if (
- result.error != null &&
- !['retries timed out', 'hit limit'].includes(
- result?.error?.message
- )
- ) {
- // ignore expected errors
- return failure.push(projectIds[i])
- } else {
- return success.push(projectIds[i])
- }
- })
- return callback(error, { success, failure, failedProjects })
- }
- )
- }
- )
- }
- )
- }
|