Skip to content

Instantly share code, notes, and snippets.

@themasch
Last active August 29, 2015 14:05
Show Gist options
  • Save themasch/b72d910081517a4115ed to your computer and use it in GitHub Desktop.
Save themasch/b72d910081517a4115ed to your computer and use it in GitHub Desktop.
node.js rate limite scheduler
function ms(input) {
if(typeof input === 'Number') {
return input
}
var number = parseInt(input, 10);
var unit = input.replace(number.toString(10), '').trim()
switch(unit) {
case 'd':
number *= 24
case 'h':
number *= 60
case 'm':
number *= 60
case 's':
number *= 1000
}
return number
}
function Scheduler(limits) {
this.smallestDelta = false
this.timerRunning = false
this.queueRunning = false
this.limits = limits.map(function(limitDef) {
var milis = ms(limitDef.time)
var limit = {
maxValue: limitDef.limit,
totalWindow: milis,
msPerTick: milis / limitDef.limit,
ticksLeft: limitDef.limit
}
limit.timer = setInterval(function() {
limit.ticksLeft = Math.min(limit.ticksLeft + 1, limit.maxValue)
}, limit.msPerTick)
if(!this.smallestDelta) {
this.smallestDelta = limit.msPerTick
}
else {
this.smallestDelta = Math.min(limit.msPerTick, this.smallestDelta)
}
// dont stay alive for this timer
limit.timer.unref()
return limit
}.bind(this))
this.queue = []
this.lastUpdate = Date.now()
}
Scheduler.prototype.push = function(fnc) {
this.queue.push(fnc)
this.resumeQueue()
}
Scheduler.prototype.resumeQueue = function() {
if(!this.queueRunning) {
console.log(Date.now(), 'resumeQueue')
setImmediate(this.execute.bind(this))
this.queueRunning = true;
}
}
Scheduler.prototype.tickAvailable = function() {
return this.limits.every(function(limit) {
return limit.ticksLeft > 0
})
}
Scheduler.prototype.execute = function() {
var hasToken = this.tickAvailable()
if(this.queue.length > 0 && hasToken) {
var max = this.limits.reduce(function(val, limit) {
return Math.min(val, limit.ticksLeft)
}, this.queue.length)
for(var i=0;i<max;i++) {
var fnc = this.queue.shift()
fnc()
}
this.limits.forEach(function(limit) {
limit.ticksLeft -= max
})
if(this.queue.length > 0) {
setImmediate(this.execute.bind(this))
}
}
else if(!hasToken) {
if(!this.timerRunning) {
// we still got work to do. keep the timers alive!
this.limits.forEach(function(limit) {
limit.timer.ref()
})
this.timerRunning = true
}
setTimeout(this.execute.bind(this), this.smallestDelta)
}
if(this.queue.length === 0) {
// shutdown queue. everything done!
this.timerRunning = this.queueRunning = false
this.limits.map(function(limit) {
limit.timer.unref()
})
}
}
module.exports = Scheduler
var Scheduler = require('./Scheduler')
var sched = new Scheduler([
{ limit: 10, time: '10s' },
{ limit: 500, time: '10m' }
])
for(var i=0;i<50;i++) {
sched.push(function() {
console.log(Date.now() - start, this.x, sched.limits.map(function(l) { return l.ticksLeft }))
}.bind({ x: i }))
}
var start = Date.now()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment