CompileManager.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. /* eslint-disable
  2. camelcase,
  3. node/handle-callback-err,
  4. max-len,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS103: Rewrite code to no longer use __guard__
  13. * DS207: Consider shorter variations of null checks
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. let CompileManager
  17. const Settings = require('settings-sharelatex')
  18. const RedisWrapper = require('../../infrastructure/RedisWrapper')
  19. const rclient = RedisWrapper.client('clsi_recently_compiled')
  20. const ProjectGetter = require('../Project/ProjectGetter')
  21. const ProjectRootDocManager = require('../Project/ProjectRootDocManager')
  22. const UserGetter = require('../User/UserGetter')
  23. const ClsiManager = require('./ClsiManager')
  24. const Metrics = require('@overleaf/metrics')
  25. const rateLimiter = require('../../infrastructure/RateLimiter')
  26. module.exports = CompileManager = {
  27. compile(project_id, user_id, options, _callback) {
  28. if (options == null) {
  29. options = {}
  30. }
  31. if (_callback == null) {
  32. _callback = function (error) {}
  33. }
  34. const timer = new Metrics.Timer('editor.compile')
  35. const callback = function (...args) {
  36. timer.done()
  37. return _callback(...Array.from(args || []))
  38. }
  39. return CompileManager._checkIfRecentlyCompiled(
  40. project_id,
  41. user_id,
  42. function (error, recentlyCompiled) {
  43. if (error != null) {
  44. return callback(error)
  45. }
  46. if (recentlyCompiled) {
  47. return callback(null, 'too-recently-compiled', [])
  48. }
  49. return CompileManager._checkIfAutoCompileLimitHasBeenHit(
  50. options.isAutoCompile,
  51. 'everyone',
  52. function (err, canCompile) {
  53. if (!canCompile) {
  54. return callback(null, 'autocompile-backoff', [])
  55. }
  56. return ProjectRootDocManager.ensureRootDocumentIsSet(
  57. project_id,
  58. function (error) {
  59. if (error != null) {
  60. return callback(error)
  61. }
  62. return CompileManager.getProjectCompileLimits(
  63. project_id,
  64. function (error, limits) {
  65. if (error != null) {
  66. return callback(error)
  67. }
  68. for (let key in limits) {
  69. const value = limits[key]
  70. options[key] = value
  71. }
  72. // Put a lower limit on autocompiles for free users, based on compileGroup
  73. return CompileManager._checkCompileGroupAutoCompileLimit(
  74. options.isAutoCompile,
  75. limits.compileGroup,
  76. function (err, canCompile) {
  77. if (!canCompile) {
  78. return callback(null, 'autocompile-backoff', [])
  79. }
  80. // only pass user_id down to clsi if this is a per-user compile
  81. const compileAsUser = Settings.disablePerUserCompiles
  82. ? undefined
  83. : user_id
  84. return ClsiManager.sendRequest(
  85. project_id,
  86. compileAsUser,
  87. options,
  88. function (
  89. error,
  90. status,
  91. outputFiles,
  92. clsiServerId,
  93. validationProblems
  94. ) {
  95. if (error != null) {
  96. return callback(error)
  97. }
  98. return callback(
  99. null,
  100. status,
  101. outputFiles,
  102. clsiServerId,
  103. limits,
  104. validationProblems
  105. )
  106. }
  107. )
  108. }
  109. )
  110. }
  111. )
  112. }
  113. )
  114. }
  115. )
  116. }
  117. )
  118. },
  119. stopCompile(project_id, user_id, callback) {
  120. if (callback == null) {
  121. callback = function (error) {}
  122. }
  123. return CompileManager.getProjectCompileLimits(
  124. project_id,
  125. function (error, limits) {
  126. if (error != null) {
  127. return callback(error)
  128. }
  129. return ClsiManager.stopCompile(project_id, user_id, limits, callback)
  130. }
  131. )
  132. },
  133. deleteAuxFiles(project_id, user_id, clsiserverid, callback) {
  134. if (callback == null) {
  135. callback = function (error) {}
  136. }
  137. return CompileManager.getProjectCompileLimits(
  138. project_id,
  139. function (error, limits) {
  140. if (error != null) {
  141. return callback(error)
  142. }
  143. ClsiManager.deleteAuxFiles(
  144. project_id,
  145. user_id,
  146. limits,
  147. clsiserverid,
  148. callback
  149. )
  150. }
  151. )
  152. },
  153. getProjectCompileLimits(project_id, callback) {
  154. if (callback == null) {
  155. callback = function (error, limits) {}
  156. }
  157. return ProjectGetter.getProject(
  158. project_id,
  159. { owner_ref: 1 },
  160. function (error, project) {
  161. if (error != null) {
  162. return callback(error)
  163. }
  164. return UserGetter.getUser(
  165. project.owner_ref,
  166. { alphaProgram: 1, betaProgram: 1, features: 1 },
  167. function (err, owner) {
  168. if (error != null) {
  169. return callback(error)
  170. }
  171. let ownerFeatures = (owner && owner.features) || {}
  172. // put alpha users into their own compile group
  173. if (owner && owner.alphaProgram) {
  174. ownerFeatures.compileGroup = 'alpha'
  175. }
  176. return callback(null, {
  177. timeout:
  178. ownerFeatures.compileTimeout ||
  179. Settings.defaultFeatures.compileTimeout,
  180. compileGroup:
  181. ownerFeatures.compileGroup ||
  182. Settings.defaultFeatures.compileGroup
  183. })
  184. }
  185. )
  186. }
  187. )
  188. },
  189. COMPILE_DELAY: 1, // seconds
  190. _checkIfRecentlyCompiled(project_id, user_id, callback) {
  191. if (callback == null) {
  192. callback = function (error, recentlyCompiled) {}
  193. }
  194. const key = `compile:${project_id}:${user_id}`
  195. return rclient.set(
  196. key,
  197. true,
  198. 'EX',
  199. this.COMPILE_DELAY,
  200. 'NX',
  201. function (error, ok) {
  202. if (error != null) {
  203. return callback(error)
  204. }
  205. if (ok === 'OK') {
  206. return callback(null, false)
  207. } else {
  208. return callback(null, true)
  209. }
  210. }
  211. )
  212. },
  213. _checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup, callback) {
  214. if (callback == null) {
  215. callback = function (err, canCompile) {}
  216. }
  217. if (!isAutoCompile) {
  218. return callback(null, true)
  219. }
  220. if (compileGroup === 'standard') {
  221. // apply extra limits to the standard compile group
  222. return CompileManager._checkIfAutoCompileLimitHasBeenHit(
  223. isAutoCompile,
  224. compileGroup,
  225. callback
  226. )
  227. } else {
  228. Metrics.inc(`auto-compile-${compileGroup}`)
  229. return callback(null, true)
  230. }
  231. }, // always allow priority group users to compile
  232. _checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup, callback) {
  233. if (callback == null) {
  234. callback = function (err, canCompile) {}
  235. }
  236. if (!isAutoCompile) {
  237. return callback(null, true)
  238. }
  239. Metrics.inc(`auto-compile-${compileGroup}`)
  240. const opts = {
  241. endpointName: 'auto_compile',
  242. timeInterval: 20,
  243. subjectName: compileGroup,
  244. throttle: Settings.rateLimit.autoCompile[compileGroup] || 25
  245. }
  246. return rateLimiter.addCount(opts, function (err, canCompile) {
  247. if (err != null) {
  248. canCompile = false
  249. }
  250. if (!canCompile) {
  251. Metrics.inc(`auto-compile-${compileGroup}-limited`)
  252. }
  253. return callback(err, canCompile)
  254. })
  255. },
  256. wordCount(project_id, user_id, file, clsiserverid, callback) {
  257. if (callback == null) {
  258. callback = function (error) {}
  259. }
  260. return CompileManager.getProjectCompileLimits(
  261. project_id,
  262. function (error, limits) {
  263. if (error != null) {
  264. return callback(error)
  265. }
  266. ClsiManager.wordCount(
  267. project_id,
  268. user_id,
  269. file,
  270. limits,
  271. clsiserverid,
  272. callback
  273. )
  274. }
  275. )
  276. }
  277. }