overleaf/services/spelling/app/js/ASpellWorkerPool.js
Jakob Ackermann 9afdb32689 [misc] bump metrics module to 3.4.1
- renamed package from `metrics-sharelatex` to `@overleaf/metrics`
- drop support for statsd backend
- decaffeinate
- compress `/metrics` response using gzip
- bump debugging agents to latest versions
- expose prometheus interfaces for custom metrics (custom tags)
- cleanup of open sockets metrics
- fix deprecation warnings for header access
2020-11-25 11:57:24 +00:00

118 lines
3.4 KiB
JavaScript

// TODO: This file was created by bulk-decaffeinate.
// Sanity-check the conversion and remove this comment.
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const ASpellWorker = require('./ASpellWorker')
const _ = require('underscore')
const logger = require('logger-sharelatex')
const metrics = require('@overleaf/metrics')
const OError = require('@overleaf/o-error')
class ASpellWorkerPool {
static initClass() {
this.prototype.MAX_REQUESTS = 100 * 1024
this.prototype.MAX_WORKERS = 32
this.prototype.MAX_IDLE_TIME = 1000
this.prototype.MAX_REQUEST_TIME = 60 * 1000
}
constructor(options) {
this.options = options
this.PROCESS_POOL = []
}
create(language) {
if (this.PROCESS_POOL.length >= this.MAX_WORKERS) {
logger.log(
{ maxworkers: this.MAX_WORKERS },
'maximum number of workers already running'
)
return null
}
const worker = new ASpellWorker(language, this.options)
worker.pipe.on('exit', () => {
if (worker.killTimer != null) {
clearTimeout(worker.killTimer)
worker.killTimer = null
}
if (worker.idleTimer != null) {
clearTimeout(worker.idleTimer)
worker.idleTimer = null
}
logger.info(
{ process: worker.pipe.pid, lang: language },
'removing aspell worker from pool'
)
return this.cleanup()
})
this.PROCESS_POOL.push(worker)
metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
return worker
}
cleanup() {
const active = this.PROCESS_POOL.filter(
(worker) => worker.state !== 'killed'
)
this.PROCESS_POOL = active
return metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
}
check(language, words, timeout, callback) {
// look for an existing process in the pool
let worker
const availableWorker = _.find(
this.PROCESS_POOL,
(cached) => cached.language === language && cached.isReady()
)
if (availableWorker == null) {
worker = this.create(language)
} else {
worker = availableWorker
}
if (worker == null) {
// return error if too many workers
callback(new OError('no worker available'))
return
}
if (worker.idleTimer != null) {
clearTimeout(worker.idleTimer)
worker.idleTimer = null
}
worker.killTimer = setTimeout(
() => worker.kill('spell check timed out'),
timeout || this.MAX_REQUEST_TIME
)
return worker.check(words, (err, output) => {
if (worker.killTimer != null) {
clearTimeout(worker.killTimer)
worker.killTimer = null
}
callback(err, output)
if (err != null) {
return
} // process has shut down
if (worker.count > this.MAX_REQUESTS) {
return worker.shutdown(`reached limit of ${this.MAX_REQUESTS} requests`)
} else {
// queue a shutdown if worker is idle
return (worker.idleTimer = setTimeout(function () {
worker.shutdown('idle worker')
return (worker.idleTimer = null)
}, this.MAX_IDLE_TIME))
}
})
}
}
ASpellWorkerPool.initClass()
module.exports = ASpellWorkerPool