ASpellWorkerPool.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. // TODO: This file was created by bulk-decaffeinate.
  2. // Sanity-check the conversion and remove this comment.
  3. /*
  4. * decaffeinate suggestions:
  5. * DS102: Remove unnecessary code created because of implicit returns
  6. * DS206: Consider reworking classes to avoid initClass
  7. * DS207: Consider shorter variations of null checks
  8. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  9. */
  10. const ASpellWorker = require('./ASpellWorker')
  11. const _ = require('underscore')
  12. const logger = require('@overleaf/logger')
  13. const metrics = require('@overleaf/metrics')
  14. const OError = require('@overleaf/o-error')
  15. class ASpellWorkerPool {
  16. static initClass() {
  17. this.prototype.MAX_REQUESTS = 100 * 1024
  18. this.prototype.MAX_WORKERS = 32
  19. this.prototype.MAX_IDLE_TIME = 1000
  20. this.prototype.MAX_REQUEST_TIME = 60 * 1000
  21. }
  22. constructor(options) {
  23. this.options = options
  24. this.PROCESS_POOL = []
  25. }
  26. create(language) {
  27. if (this.PROCESS_POOL.length >= this.MAX_WORKERS) {
  28. logger.debug(
  29. { maxworkers: this.MAX_WORKERS },
  30. 'maximum number of workers already running'
  31. )
  32. return null
  33. }
  34. const worker = new ASpellWorker(language, this.options)
  35. worker.pipe.on('exit', () => {
  36. if (worker.killTimer != null) {
  37. clearTimeout(worker.killTimer)
  38. worker.killTimer = null
  39. }
  40. if (worker.idleTimer != null) {
  41. clearTimeout(worker.idleTimer)
  42. worker.idleTimer = null
  43. }
  44. logger.debug(
  45. { process: worker.pipe.pid, lang: language },
  46. 'removing aspell worker from pool'
  47. )
  48. return this.cleanup()
  49. })
  50. this.PROCESS_POOL.push(worker)
  51. metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  52. return worker
  53. }
  54. cleanup() {
  55. const active = this.PROCESS_POOL.filter(worker => worker.state !== 'killed')
  56. this.PROCESS_POOL = active
  57. return metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  58. }
  59. check(language, words, timeout, callback) {
  60. // look for an existing process in the pool
  61. let worker
  62. const availableWorker = _.find(
  63. this.PROCESS_POOL,
  64. cached => cached.language === language && cached.isReady()
  65. )
  66. if (availableWorker == null) {
  67. worker = this.create(language)
  68. } else {
  69. worker = availableWorker
  70. }
  71. if (worker == null) {
  72. // return error if too many workers
  73. callback(new OError('no worker available'))
  74. return
  75. }
  76. if (worker.idleTimer != null) {
  77. clearTimeout(worker.idleTimer)
  78. worker.idleTimer = null
  79. }
  80. worker.killTimer = setTimeout(
  81. () => worker.kill('spell check timed out'),
  82. timeout || this.MAX_REQUEST_TIME
  83. )
  84. return worker.check(words, (err, output) => {
  85. if (worker.killTimer != null) {
  86. clearTimeout(worker.killTimer)
  87. worker.killTimer = null
  88. }
  89. callback(err, output)
  90. if (err != null) {
  91. return
  92. } // process has shut down
  93. if (worker.count > this.MAX_REQUESTS) {
  94. return worker.shutdown(`reached limit of ${this.MAX_REQUESTS} requests`)
  95. } else {
  96. // queue a shutdown if worker is idle
  97. return (worker.idleTimer = setTimeout(function () {
  98. worker.shutdown('idle worker')
  99. return (worker.idleTimer = null)
  100. }, this.MAX_IDLE_TIME))
  101. }
  102. })
  103. }
  104. }
  105. ASpellWorkerPool.initClass()
  106. module.exports = ASpellWorkerPool