forked from Strider-CD/strider-simple-runner
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjobqueue.js
More file actions
114 lines (90 loc) · 3.32 KB
/
Copy pathjobqueue.js
File metadata and controls
114 lines (90 loc) · 3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
var branchFromJob = require('./utils').branchFromJob
module.exports = JobQueue
function JobQueue(handler, concurrency) {
this.concurrency = concurrency
this.handler = handler
this.tasks = []
this.active = {}
this.drainCallback = null
}
JobQueue.prototype = {
// public api
// Add a job to the end of the queue. If the queue is not currently saturated, immediately
// schedule a task to handle the new job. If a callback is provided, call it when this job's task
// completes.
push: function (job, config, callback) {
var task = {
job: job,
config: config,
callback: callback || function () {}
}
task.id = task.job._id
// Tasks with identical keys will be prevented from being scheduled concurrently.
task.key = task.job.project + branchFromJob(task.job)
this.tasks.push(task)
// Defer task execution to the next event loop tick to ensure that the push() function's
// callback is *always* invoked asynchronously.
// http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
process.nextTick(this.drain.bind(this))
},
// Launch the asynchronous handler function for each eligible waiting task until the queue is
// saturated.
drain: function () {
var self = this
// See how much capacity we have left to fill.
var launchCount = this.concurrency - Object.keys(this.active).length
// Identify up to launchCount eligible tasks, giving priority to those earlier in the queue.
var offset = 0
var launchTasks = []
while (launchTasks.length < launchCount && this.tasks.length > offset) {
var task = this.tasks[offset]
if (task.key in this.active) {
// This task cannot run right now, so skip it.
offset += 1
} else {
// This task is eligible to run. Remove it from the queue and prepare it to launch.
this.tasks.splice(offset, 1)
launchTasks.push(task)
}
}
// Create a task completion callback. Remove the task from the active set, invoke the tasks'
// push() callback, then drain() again to see if another task is ready to run.
var makeTaskHandler = function (task) {
return function (err) {
delete self.active[task.key]
task.callback(err)
// Defer the next drain() call again in case the task's callback was synchronous.
process.nextTick(self.drain.bind(self))
}
}
// Launch the queue handler for each chosen task.
for (var i = 0; i < launchTasks.length; i++) {
var each = launchTasks[i]
this.active[each.key] = each
this.handler(each.job, each.config, makeTaskHandler(each))
}
// Fire and unset the drain callback if one has been registered.
if (this.drainCallback) {
var lastCallback = this.drainCallback
this.drainCallback = null
lastCallback()
}
},
// Count the number of tasks waiting on the queue.
length: function () {
return this.tasks.length
},
// Return true if "id" corresponds to the job ID of an active job.
isActive: function (id) {
for (var key in this.active) {
if (this.active.hasOwnProperty(key) && this.active[key].id === id) {
return true
}
}
return false
},
// Fire a callback the next time that a drain() is executed.
onNextDrain: function (callback) {
this.drainCallback = callback
}
}