CompileManager.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /* eslint-disable
  2. camelcase,
  3. 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('metrics-sharelatex')
  25. const logger = require('logger-sharelatex')
  26. const rateLimiter = require('../../infrastructure/RateLimiter')
  27. module.exports = CompileManager = {
  28. compile(project_id, user_id, options, _callback) {
  29. if (options == null) {
  30. options = {}
  31. }
  32. if (_callback == null) {
  33. _callback = function(error) {}
  34. }
  35. const timer = new Metrics.Timer('editor.compile')
  36. const callback = function(...args) {
  37. timer.done()
  38. return _callback(...Array.from(args || []))
  39. }
  40. return CompileManager._checkIfRecentlyCompiled(
  41. project_id,
  42. user_id,
  43. function(error, recentlyCompiled) {
  44. if (error != null) {
  45. return callback(error)
  46. }
  47. if (recentlyCompiled) {
  48. logger.warn(
  49. { project_id, user_id },
  50. 'project was recently compiled so not continuing'
  51. )
  52. return callback(null, 'too-recently-compiled', [])
  53. }
  54. return CompileManager._checkIfAutoCompileLimitHasBeenHit(
  55. options.isAutoCompile,
  56. 'everyone',
  57. function(err, canCompile) {
  58. if (!canCompile) {
  59. return callback(null, 'autocompile-backoff', [])
  60. }
  61. return ProjectRootDocManager.ensureRootDocumentIsValid(
  62. project_id,
  63. function(error) {
  64. if (error != null) {
  65. return callback(error)
  66. }
  67. return CompileManager.getProjectCompileLimits(
  68. project_id,
  69. function(error, limits) {
  70. if (error != null) {
  71. return callback(error)
  72. }
  73. for (let key in limits) {
  74. const value = limits[key]
  75. options[key] = value
  76. }
  77. // Put a lower limit on autocompiles for free users, based on compileGroup
  78. return CompileManager._checkCompileGroupAutoCompileLimit(
  79. options.isAutoCompile,
  80. limits.compileGroup,
  81. function(err, canCompile) {
  82. if (!canCompile) {
  83. return callback(null, 'autocompile-backoff', [])
  84. }
  85. // only pass user_id down to clsi if this is a per-user compile
  86. const compileAsUser = Settings.disablePerUserCompiles
  87. ? undefined
  88. : user_id
  89. return ClsiManager.sendRequest(
  90. project_id,
  91. compileAsUser,
  92. options,
  93. function(
  94. error,
  95. status,
  96. outputFiles,
  97. clsiServerId,
  98. validationProblems
  99. ) {
  100. if (error != null) {
  101. return callback(error)
  102. }
  103. return callback(
  104. null,
  105. status,
  106. outputFiles,
  107. clsiServerId,
  108. limits,
  109. validationProblems
  110. )
  111. }
  112. )
  113. }
  114. )
  115. }
  116. )
  117. }
  118. )
  119. }
  120. )
  121. }
  122. )
  123. },
  124. stopCompile(project_id, user_id, callback) {
  125. if (callback == null) {
  126. callback = function(error) {}
  127. }
  128. return CompileManager.getProjectCompileLimits(project_id, function(
  129. error,
  130. limits
  131. ) {
  132. if (error != null) {
  133. return callback(error)
  134. }
  135. return ClsiManager.stopCompile(project_id, user_id, limits, callback)
  136. })
  137. },
  138. deleteAuxFiles(project_id, user_id, callback) {
  139. if (callback == null) {
  140. callback = function(error) {}
  141. }
  142. return CompileManager.getProjectCompileLimits(project_id, function(
  143. error,
  144. limits
  145. ) {
  146. if (error != null) {
  147. return callback(error)
  148. }
  149. return ClsiManager.deleteAuxFiles(project_id, user_id, limits, callback)
  150. })
  151. },
  152. getProjectCompileLimits(project_id, callback) {
  153. if (callback == null) {
  154. callback = function(error, limits) {}
  155. }
  156. return ProjectGetter.getProject(project_id, { owner_ref: 1 }, function(
  157. error,
  158. project
  159. ) {
  160. if (error != null) {
  161. return callback(error)
  162. }
  163. return UserGetter.getUser(project.owner_ref, { features: 1 }, function(
  164. err,
  165. owner
  166. ) {
  167. if (error != null) {
  168. return callback(error)
  169. }
  170. return callback(null, {
  171. timeout:
  172. __guard__(
  173. owner != null ? owner.features : undefined,
  174. x => x.compileTimeout
  175. ) || Settings.defaultFeatures.compileTimeout,
  176. compileGroup:
  177. __guard__(
  178. owner != null ? owner.features : undefined,
  179. x1 => x1.compileGroup
  180. ) || Settings.defaultFeatures.compileGroup
  181. })
  182. })
  183. })
  184. },
  185. COMPILE_DELAY: 1, // seconds
  186. _checkIfRecentlyCompiled(project_id, user_id, callback) {
  187. if (callback == null) {
  188. callback = function(error, recentlyCompiled) {}
  189. }
  190. const key = `compile:${project_id}:${user_id}`
  191. return rclient.set(key, true, 'EX', this.COMPILE_DELAY, 'NX', function(
  192. error,
  193. ok
  194. ) {
  195. if (error != null) {
  196. return callback(error)
  197. }
  198. if (ok === 'OK') {
  199. return callback(null, false)
  200. } else {
  201. return callback(null, true)
  202. }
  203. })
  204. },
  205. _checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup, callback) {
  206. if (callback == null) {
  207. callback = function(err, canCompile) {}
  208. }
  209. if (!isAutoCompile) {
  210. return callback(null, true)
  211. }
  212. if (compileGroup === 'standard') {
  213. // apply extra limits to the standard compile group
  214. return CompileManager._checkIfAutoCompileLimitHasBeenHit(
  215. isAutoCompile,
  216. compileGroup,
  217. callback
  218. )
  219. } else {
  220. Metrics.inc(`auto-compile-${compileGroup}`)
  221. return callback(null, true)
  222. }
  223. }, // always allow priority group users to compile
  224. _checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup, callback) {
  225. if (callback == null) {
  226. callback = function(err, canCompile) {}
  227. }
  228. if (!isAutoCompile) {
  229. return callback(null, true)
  230. }
  231. Metrics.inc(`auto-compile-${compileGroup}`)
  232. const opts = {
  233. endpointName: 'auto_compile',
  234. timeInterval: 20,
  235. subjectName: compileGroup,
  236. throttle: Settings.rateLimit.autoCompile[compileGroup] || 25
  237. }
  238. return rateLimiter.addCount(opts, function(err, canCompile) {
  239. if (err != null) {
  240. canCompile = false
  241. }
  242. if (!canCompile) {
  243. Metrics.inc(`auto-compile-${compileGroup}-limited`)
  244. }
  245. return callback(err, canCompile)
  246. })
  247. },
  248. wordCount(project_id, user_id, file, callback) {
  249. if (callback == null) {
  250. callback = function(error) {}
  251. }
  252. return CompileManager.getProjectCompileLimits(project_id, function(
  253. error,
  254. limits
  255. ) {
  256. if (error != null) {
  257. return callback(error)
  258. }
  259. return ClsiManager.wordCount(project_id, user_id, file, limits, callback)
  260. })
  261. }
  262. }
  263. function __guard__(value, transform) {
  264. return typeof value !== 'undefined' && value !== null
  265. ? transform(value)
  266. : undefined
  267. }