concurrent-seq-file
Version:
makes it safe to work on multiple jobs identified by a sequence at the same time.
134 lines (110 loc) • 3.14 kB
JavaScript
var Sf = require('seq-file')
module.exports = function (file, options) {
options = options||{}
var sf = new Sf(file, options)
patchSeq(sf)
var saveValue = sf.readSync()
// if seq is 0 strings that start with 0 are always false when compared to it.
// https://github.com/npm/seq-file/pull/7
if(sf.seq == 0) sf.seq = ''
options.savedValue = saveValue||0
var start = module.exports.starter(function(toSave,cb){
// save the new sequence via seq-file
save(sf, toSave, cb)
},options)
start.value = saveValue
return start
}
module.exports.starter = function(persist,options) {
options = options || {}
var saveValue = options.savedValue||0
var delay = options.delay || 1000
var started = {}
var dones = {}
var saving = false
var saveQ = false
return function startSequence (seq, opts) {
started[seq] = seq
return function (cb) {
delete started[seq]
var startedSeq = least(started)
if (dones[seq]) {
var msg = "shouldn't 'done' the same sequence id more than once concurrently"
if(cb) cb(new Error(msg))
else console.warn(msg)
return
}
dones[seq] = {cb:cb, seq:seq}
// always save the highest number in the dones that's less than the lowest number in the actives
var endSeq = ''
var cbs = []
// sort
Object.keys(dones).sort().forEach(function (k) {
if (!startedSeq || (dones[k].seq < startedSeq && endSeq <= dones[k].seq)) {
endSeq = dones[k].seq
cbs.push(dones[k].cb)
delete dones[k]
}
})
if (!endSeq || endSeq === saveValue) {
return unroll(false, saveValue, cbs)
}
saveValue = endSeq
if (!saveQ) saveQ = []
// im either already waiting or my save loop is idle and the implementor has not yet completed the lowest started sequence
if (saving || !saveValue) {
return saveQ.push.apply(saveQ, cbs)
}
saveLoop(cbs)
function saveLoop (cbs) {
saving = true
var savedValue = saveValue
persist(savedValue, function (err){
startSequence.value = savedValue
unroll(err, savedValue, cbs)
setTimeout(function () {
saving = false
if (saveQ.length) {
cbs = saveQ
saveQ = []
saveLoop(cbs)
}
}, delay)
})
}
}
}
}
function save (sf, seq, cb) {
sf.savecbs.push(function (err) {
if (cb) cb(err)
})
sf.save(seq)
}
function patchSeq (sf) {
if (sf.savecbs) return
sf.savecbs = []
var ofinish = sf.onFinish
sf.onFinish = function (err) {
ofinish.apply(this, arguments)
var cbs = sf.savecbs
sf.savecbs = []
if (cbs) while (cbs.length) cbs.shift()(err)
}
}
function least (obj) {
var v = ''
Object.keys(obj).forEach(function (k) {
k = obj[k]
if (!v || v > k) v = k
})
return v
}
function unroll (err, data, cbs) {
var cb
while (cbs.length) {
cb = cbs.shift()
if(cb) cb(err, data)
}
}
function noop () {}