ASpellWorkerPool.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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('logger-sharelatex')
  13. const metrics = require('metrics-sharelatex')
  14. class ASpellWorkerPool {
  15. static initClass() {
  16. this.prototype.MAX_REQUESTS = 100 * 1024
  17. this.prototype.MAX_WORKERS = 32
  18. this.prototype.MAX_IDLE_TIME = 1000
  19. this.prototype.MAX_REQUEST_TIME = 60 * 1000
  20. }
  21. constructor(options) {
  22. this.options = options
  23. this.PROCESS_POOL = []
  24. }
  25. create(language) {
  26. if (this.PROCESS_POOL.length >= this.MAX_WORKERS) {
  27. logger.log(
  28. { maxworkers: this.MAX_WORKERS },
  29. 'maximum number of workers already running'
  30. )
  31. return null
  32. }
  33. const worker = new ASpellWorker(language, this.options)
  34. worker.pipe.on('exit', () => {
  35. if (worker.killTimer != null) {
  36. clearTimeout(worker.killTimer)
  37. worker.killTimer = null
  38. }
  39. if (worker.idleTimer != null) {
  40. clearTimeout(worker.idleTimer)
  41. worker.idleTimer = null
  42. }
  43. logger.info(
  44. { process: worker.pipe.pid, lang: language },
  45. 'removing aspell worker from pool'
  46. )
  47. return this.cleanup()
  48. })
  49. this.PROCESS_POOL.push(worker)
  50. metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  51. return worker
  52. }
  53. cleanup() {
  54. const active = this.PROCESS_POOL.filter(worker => worker.state !== 'killed')
  55. this.PROCESS_POOL = active
  56. return metrics.gauge('aspellWorkerPool-size', this.PROCESS_POOL.length)
  57. }
  58. check(language, words, timeout, callback) {
  59. // look for an existing process in the pool
  60. let worker
  61. const availableWorker = _.find(
  62. this.PROCESS_POOL,
  63. cached => cached.language === language && cached.isReady()
  64. )
  65. if (availableWorker == null) {
  66. worker = this.create(language)
  67. } else {
  68. worker = availableWorker
  69. }
  70. if (worker == null) {
  71. // return error if too many workers
  72. callback(new Error('no worker available'))
  73. return
  74. }
  75. if (worker.idleTimer != null) {
  76. clearTimeout(worker.idleTimer)
  77. worker.idleTimer = null
  78. }
  79. worker.killTimer = setTimeout(
  80. () => worker.kill('spell check timed out'),
  81. timeout || this.MAX_REQUEST_TIME
  82. )
  83. return worker.check(words, (err, output) => {
  84. if (worker.killTimer != null) {
  85. clearTimeout(worker.killTimer)
  86. worker.killTimer = null
  87. }
  88. callback(err, output)
  89. if (err != null) {
  90. return
  91. } // process has shut down
  92. if (worker.count > this.MAX_REQUESTS) {
  93. return worker.shutdown(`reached limit of ${this.MAX_REQUESTS} requests`)
  94. } else {
  95. // queue a shutdown if worker is idle
  96. return (worker.idleTimer = setTimeout(function() {
  97. worker.shutdown('idle worker')
  98. return (worker.idleTimer = null)
  99. }, this.MAX_IDLE_TIME))
  100. }
  101. })
  102. }
  103. }
  104. ASpellWorkerPool.initClass()
  105. module.exports = ASpellWorkerPool