@@ -112,36 +112,33 @@ export default function (tasks, concurrency, callback) {
112
112
113
113
var readyTasks = [ ] ;
114
114
115
+ // for cycle detection:
116
+ var readyToCheck = [ ] ; // tasks that have been identified as reachable
117
+ // without the possibility of returning to an ancestor task
118
+ var uncheckedDependencies = { } ;
115
119
116
120
forOwn ( tasks , function ( task , key ) {
117
121
if ( ! isArray ( task ) ) {
118
122
// no dependencies
119
123
enqueueTask ( key , [ task ] ) ;
124
+ readyToCheck . push ( key ) ;
120
125
return ;
121
126
}
122
127
123
128
var dependencies = task . slice ( 0 , task . length - 1 ) ;
124
129
var remainingDependencies = dependencies . length ;
125
-
126
- checkForDeadlocks ( ) ;
127
-
128
- function checkForDeadlocks ( ) {
129
- var len = dependencies . length ;
130
- var dep ;
131
- while ( len -- ) {
132
- if ( ! ( dep = tasks [ dependencies [ len ] ] ) ) {
133
- throw new Error ( 'async.auto task `' + key +
134
- '` has non-existent dependency in ' +
135
- dependencies . join ( ', ' ) ) ;
136
- }
137
- if ( isArray ( dep ) && indexOf ( dep , key , 0 ) >= 0 ) {
138
- throw new Error ( 'async.auto task `' + key +
139
- '`Has cyclic dependencies' ) ;
140
- }
141
- }
130
+ if ( ! remainingDependencies ) {
131
+ enqueueTask ( key , [ task ] ) ;
132
+ readyToCheck . push ( key ) ;
142
133
}
134
+ uncheckedDependencies [ key ] = remainingDependencies ;
143
135
144
136
arrayEach ( dependencies , function ( dependencyName ) {
137
+ if ( ! tasks [ dependencyName ] ) {
138
+ throw new Error ( 'async.auto task `' + key +
139
+ '` has a non-existent dependency in ' +
140
+ dependencies . join ( ', ' ) ) ;
141
+ }
145
142
addListener ( dependencyName , function ( ) {
146
143
remainingDependencies -- ;
147
144
if ( remainingDependencies === 0 ) {
@@ -151,9 +148,9 @@ export default function (tasks, concurrency, callback) {
151
148
} ) ;
152
149
} ) ;
153
150
151
+ checkForDeadlocks ( ) ;
154
152
processQueue ( ) ;
155
153
156
-
157
154
function enqueueTask ( key , task ) {
158
155
readyTasks . push ( function ( ) {
159
156
runTask ( key , task ) ;
@@ -222,5 +219,36 @@ export default function (tasks, concurrency, callback) {
222
219
}
223
220
}
224
221
222
+ function checkForDeadlocks ( ) {
223
+ // Kahn's algorithm
224
+ // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
225
+ // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
226
+ var currentTask ;
227
+ var counter = 0 ;
228
+ while ( readyToCheck . length ) {
229
+ currentTask = readyToCheck . pop ( ) ;
230
+ counter ++ ;
231
+ arrayEach ( getDependents ( currentTask ) , function ( dependent ) {
232
+ if ( ! ( -- uncheckedDependencies [ dependent ] ) ) {
233
+ readyToCheck . push ( dependent ) ;
234
+ }
235
+ } ) ;
236
+ }
237
+
238
+ if ( counter !== numTasks ) {
239
+ throw new Error (
240
+ 'async.auto cannot execute tasks due to a recursive dependency'
241
+ ) ;
242
+ }
243
+ }
225
244
245
+ function getDependents ( taskName ) {
246
+ var result = [ ] ;
247
+ forOwn ( tasks , function ( task , key ) {
248
+ if ( isArray ( task ) && indexOf ( task , taskName , 0 ) >= 0 ) {
249
+ result . push ( key ) ;
250
+ }
251
+ } ) ;
252
+ return result ;
253
+ }
226
254
}
0 commit comments