@railzai/bottleneck
Version:
Distributed task scheduler and rate limiter
159 lines (134 loc) • 5.98 kB
text/coffeescript
parser = require "./parser"
BottleneckError = require "./BottleneckError"
RedisConnection = require "./RedisConnection"
IORedisConnection = require "./IORedisConnection"
class RedisDatastore
constructor: ( , , storeInstanceOptions) ->
= .id
= ._randomIndex()
parser.load storeInstanceOptions, storeInstanceOptions, @
= {}
= {}
= ?
?= if .datastore == "redis" then new RedisConnection { , , , Events: .Events }
else if .datastore == "ioredis" then new IORedisConnection { , , , , Events: .Events }
.connection =
.datastore = .datastore
= .ready
.then ( ) => "init",
.then => .__addLimiter__
.then => "register_client", [ .queued()]
.then =>
( = setInterval =>
"heartbeat", []
.catch (e) => .Events.trigger "error", e
, ).unref?()
__publish__: (message) ->
{ client } = await
client.publish( .channel(), "message:#{message.toString()}")
onMessage: (channel, message) ->
try
pos = message.indexOf(":")
[type, data] = [message.slice(0, pos), message.slice(pos+1)]
if type == "capacity"
await ._drainAll(if data.length > 0 then ~~data)
else if type == "capacity-priority"
[rawCapacity, priorityClient, counter] = data.split(":")
capacity = if rawCapacity.length > 0 then ~~rawCapacity
if priorityClient ==
drained = await ._drainAll(capacity)
newCapacity = if capacity? then capacity - (drained or 0) else ""
await .client.publish( .channel(), "capacity-priority:#{newCapacity}::#{counter}")
else if priorityClient == ""
clearTimeout [counter]
delete [counter]
._drainAll(capacity)
else
[counter] = setTimeout =>
try
delete [counter]
await "blacklist_client", [priorityClient]
await ._drainAll(capacity)
catch e then .Events.trigger "error", e
, 1000
else if type == "message"
.Events.trigger "message", data
else if type == "blocked"
await ._dropAllQueued()
catch e then .Events.trigger "error", e
__disconnect__: (flush) ->
clearInterval
if
.__removeLimiter__
else
.disconnect flush
runScript: (name, args) ->
await unless name == "init" or name == "register_client"
new (resolve, reject) =>
all_args = [Date.now(), ].concat args
.Events.trigger "debug", "Calling Redis script: #{name}.lua", all_args
arr = .__scriptArgs__ name, , all_args, (err, replies) ->
if err? then return reject err
return resolve replies
.__scriptFn__(name) arr...
.catch (e) =>
if (e.message.match(/^(.*\s)?SETTINGS_KEY_NOT_FOUND$/) != null)
if name == "heartbeat" then .resolve()
else
("init", (false))
.then => (name, args)
else if (e.message.match(/^(.*\s)?UNKNOWN_CLIENT$/) != null)
("register_client", [ .queued()])
.then => (name, args)
else .reject e
prepareArray: (arr) -> (if x? then x.toString() else "") for x in arr
prepareObject: (obj) ->
arr = []
for k, v of obj then arr.push k, (if v? then v.toString() else "")
arr
prepareInitSettings: (clear) ->
args = Object.assign({}, , {
id:
version: .version
groupTimeout:
})
args.unshift (if clear then 1 else 0), .version
args
convertBool: (b) -> !!b
__updateSettings__: (options) ->
await "update_settings", options
parser.overwrite options, options,
__running__: -> "running", []
__queued__: -> "queued", []
__done__: -> "done", []
__groupCheck__: -> await "group_check", []
__incrementReservoir__: (incr) -> "increment_reservoir", [incr]
__currentReservoir__: -> "current_reservoir", []
__check__: (weight) -> await "check", [weight]
__register__: (index, weight, expiration) ->
[success, wait, reservoir] = await "register", [index, weight, expiration]
return {
success: (success),
wait,
reservoir
}
__submit__: (queueLength, weight) ->
try
[reachedHWM, blocked, strategy] = await "submit", [queueLength, weight]
return {
reachedHWM: (reachedHWM),
blocked: (blocked),
strategy
}
catch e
if e.message.indexOf("OVERWEIGHT") == 0
[overweight, weight, maxConcurrent] = e.message.split ":"
throw new BottleneckError("Impossible to add a job having a weight of #{weight} to a limiter having a maxConcurrent setting of #{maxConcurrent}")
else
throw e
__free__: (index, weight) ->
running = await "free", [index]
return { running }
module.exports = RedisDatastore