Advertisement
Guest User

Untitled

a guest
May 23rd, 2019
102
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.86 KB | None | 0 0
  1. /**
  2. * Experimental optimistically-locking cache-manager store wrapper
  3. *
  4. * Completely untested; caveat emptor
  5. */
  6.  
  7. const inspect = require('util').inspect
  8.  
  9. export const LOCK_TIMEOUT = 5000
  10. export const LOCK_EXTEND_TIMEOUT = 2500
  11. export const LOCK_MAX_EXTENSIONS = 100
  12.  
  13. function log (...message) {
  14. if (process.env.NODE_ENV === 'test' && !process.env.DEBUG) return
  15. message = [new Date(), ...message]
  16. console.log(...message.map((m) => {
  17. if (typeof m === 'string') return m
  18. if (m instanceof Error) return m.stack
  19. if (m instanceof Date) return m.toLocaleString()
  20. return inspect(m, { colors: Boolean(process.stdout.isTTY) })
  21. }))
  22. }
  23.  
  24. log.debug = function logDebug (...message) {
  25. if (process.env.NODE_ENV !== 'debug') return
  26. log(...message)
  27. }
  28.  
  29. export class LockMaxExtensionsReached extends Error {}
  30.  
  31. export class ExtendableLock {
  32. constructor ({
  33. redlock,
  34. autoExtend = true,
  35. maxExtensions = LOCK_MAX_EXTENSIONS,
  36. extendInterval = LOCK_EXTEND_TIMEOUT
  37. }) {
  38. this.redlock = redlock
  39. this.autoExtend = autoExtend
  40. this.maxExtensions = maxExtensions
  41. this.extensionsRemaining = 0
  42. this.lock = null
  43. this._autoExtendTimeoutHandle = null
  44. }
  45.  
  46. async _autoExtendTimeout () {
  47. try {
  48. await this.extend()
  49. } catch (err) {
  50. if (err instanceof LockMaxExtensionsReached) {
  51. return
  52. }
  53. throw err
  54. }
  55. this._autoExtendTimeoutHandle = setTimeout(
  56. this._autoExtendTimeout.bind(this),
  57. LOCK_EXTEND_TIMEOUT
  58. )
  59. }
  60.  
  61. _clearTimeout () {
  62. clearTimeout(this._autoExtendTimeoutHandle)
  63. }
  64.  
  65. async lock (...opts) {
  66. if (this.extensionsRemaining > 0) return
  67. this.unlocked = new Promise()
  68. this.extensionsRemaining = this.maxExtensions
  69. this.lock = await this.redlock.lock(...opts)
  70. this.autoExtend && this._autoExtendTimeout()
  71. }
  72.  
  73. unlock (...opts) {
  74. this._clearTimeout()
  75. this.extensionsRemaining = 0
  76. const lock = this.lock
  77. this.lock = null
  78. this.unlocked.resolve()
  79. return lock.unlock(...opts)
  80. }
  81.  
  82. extend (...opts) {
  83. if (this.extensionsRemaining === 0) {
  84. throw new LockMaxExtensionsReached()
  85. }
  86. this.extensionsRemaining--
  87. return this.lock.extend(...opts)
  88. }
  89. }
  90.  
  91. export default class LockingStore {
  92. constructor ({
  93. store,
  94. redlock,
  95. namespace = 'default',
  96. timeout = LOCK_TIMEOUT,
  97. extendInterval = LOCK_EXTEND_TIMEOUT
  98. }) {
  99. this.namespace = namespace
  100. this.store = store
  101. this.redlock = redlock
  102. this.timeout = timeout
  103. this.extendInterval = extendInterval
  104. this._locks = new Map()
  105. }
  106.  
  107. async lock (key) {
  108. let lock = this._locks.get(key)
  109. if (lock) {
  110. await lock.unlocked
  111. }
  112. lock = new ExtendableLock({ redlock: this.redlock })
  113. await lock.lock(`locking-store/${this.namespace}/${key}`, LOCK_TIMEOUT)
  114. this._locks.set(key, lock)
  115. return lock
  116. }
  117.  
  118. async unlock (key) {
  119. const lock = this._locks.get(key)
  120. this._locks.delete(key)
  121. const r = lock.unlock()
  122. return r
  123. }
  124.  
  125. async get (key, opts, cb) {
  126. if (typeof opts === 'function') {
  127. cb = opts
  128. opts = undefined
  129. }
  130. opts = opts || {}
  131. cb = cb || (() => {})
  132. try {
  133. let value = await this.store.get(key, opts)
  134. if (value === null) {
  135. await this.lock(key)
  136. }
  137. try {
  138. if (value === null) {
  139. value = await this.store.get(key, opts)
  140. }
  141. cb(null, value)
  142. return value
  143. } finally {
  144. if (value !== null) {
  145. this.unlock(key)
  146. }
  147. }
  148. } catch (err) {
  149. cb(err, null)
  150. throw err
  151. }
  152. }
  153.  
  154. async set (key, value, opts, cb) {
  155. if (typeof opts === 'function') {
  156. cb = opts
  157. opts = undefined
  158. }
  159. opts = opts || {}
  160. cb = cb || (() => {})
  161. try {
  162. const v = await this.store.set(key, value, opts)
  163. cb(null, v)
  164. return v
  165. } catch (err) {
  166. cb(err, null)
  167. } finally {
  168. this.unlock(key)
  169. }
  170. }
  171. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement