| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- /* eslint-disable
- no-unused-vars,
- */
- // 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
- */
- let DeleteQueueManager
- const Settings = require('@overleaf/settings')
- const RedisManager = require('./RedisManager')
- const ProjectManager = require('./ProjectManager')
- const logger = require('@overleaf/logger')
- const metrics = require('./Metrics')
- // Maintain a sorted set of project flushAndDelete requests, ordered by timestamp
- // (ZADD), and process them from oldest to newest. A flushAndDelete request comes
- // from real-time and is triggered when a user leaves a project.
- //
- // The aim is to remove the project from redis 5 minutes after the last request
- // if there has been no activity (document updates) in that time. If there is
- // activity we can expect a further flushAndDelete request when the editing user
- // leaves the project.
- //
- // If a new flushAndDelete request comes in while an existing request is already
- // in the queue we update the timestamp as we can postpone flushing further.
- //
- // Documents are processed by checking the queue, seeing if the first entry is
- // older than 5 minutes, and popping it from the queue in that case.
- module.exports = DeleteQueueManager = {
- flushAndDeleteOldProjects(options, callback) {
- const startTime = Date.now()
- const cutoffTime =
- startTime - options.min_delete_age + 100 * (Math.random() - 0.5)
- let count = 0
- const flushProjectIfNotModified = (projectId, flushTimestamp, cb) =>
- ProjectManager.getProjectDocsTimestamps(
- projectId,
- function (err, timestamps) {
- if (err != null) {
- return callback(err)
- }
- if (timestamps.length === 0) {
- logger.debug(
- { projectId },
- 'skipping flush of queued project - no timestamps'
- )
- return cb()
- }
- // are any of the timestamps newer than the time the project was flushed?
- for (const timestamp of Array.from(timestamps)) {
- if (timestamp > flushTimestamp) {
- metrics.inc('queued-delete-skipped')
- logger.debug(
- { projectId, timestamps, flushTimestamp },
- 'found newer timestamp, will skip delete'
- )
- return cb()
- }
- }
- logger.debug({ projectId, flushTimestamp }, 'flushing queued project')
- return ProjectManager.flushAndDeleteProjectWithLocks(
- projectId,
- { skip_history_flush: false },
- function (err) {
- if (err != null) {
- logger.err({ projectId, err }, 'error flushing queued project')
- }
- metrics.inc('queued-delete-completed')
- return cb(null, true)
- }
- )
- }
- )
- function flushNextProject() {
- const now = Date.now()
- if (now - startTime > options.timeout) {
- logger.debug('hit time limit on flushing old projects')
- return callback(null, count)
- }
- if (count > options.limit) {
- logger.debug('hit count limit on flushing old projects')
- return callback(null, count)
- }
- return RedisManager.getNextProjectToFlushAndDelete(
- cutoffTime,
- function (err, projectId, flushTimestamp, queueLength) {
- if (err != null) {
- return callback(err, count)
- }
- if (projectId == null) {
- return callback(null, count)
- }
- logger.debug({ projectId, queueLength }, 'flushing queued project')
- metrics.globalGauge('queued-flush-backlog', queueLength)
- return flushProjectIfNotModified(
- projectId,
- flushTimestamp,
- function (err, flushed) {
- if (err) {
- // Do not stop processing the queue in case the flush fails.
- // Slowing down the processing can fill up redis.
- metrics.inc('queued-delete-error')
- }
- if (flushed) {
- count++
- }
- return flushNextProject()
- }
- )
- }
- )
- }
- return flushNextProject()
- },
- startBackgroundFlush() {
- const SHORT_DELAY = 10
- const LONG_DELAY = 1000
- function doFlush() {
- if (Settings.shuttingDown) {
- logger.info('discontinuing background flush due to shutdown')
- return
- }
- return DeleteQueueManager.flushAndDeleteOldProjects(
- {
- timeout: 1000,
- min_delete_age: 3 * 60 * 1000,
- limit: 1000, // high value, to ensure we always flush enough projects
- },
- (_err, flushed) =>
- setTimeout(doFlush, flushed > 10 ? SHORT_DELAY : LONG_DELAY)
- )
- }
- return doFlush()
- },
- }
|