Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/task.js

Issue 9615013: Crawler, first version (Closed)
Patch Set: Created March 6, 2013, 4:05 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« .hgignore ('K') | « lib/storage.js ('k') | lib/yaml.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 let {Logger} = require( "logger" );
2 /**
3 * The default value for runaway_limit, used only in the constructor.
4 * @type {Number}
5 */
6 const default_runaway_limit = 1000;
7
8 /**
9 * Class for long-running tasks. Such tasks divide work into discrete units of e ffort, which allows them to be
10 * interrupted and to post progress.
11 * <p/>
12 * Note that are no callback or events in this class. Any state feedback should be implemented as needed within the
13 * task generator. task generator can distinguish cancellation by examining the rvalue of its yield statements. The
14 * generator cannot detect being paused, but the control object that started the task can.
15 * <p/>
16 * PLANNED: The current version will handle AJAX calls by polling the task gener ator every millisecond to see if there
17 * have been pending calls that have completed since the last poll. It would mak e for better performance for the task
18 * to signal that it should pause execution and then continue only after a pendi ng call completes. This would eliminate
19 * run-time polling overhead.
20 * <p/>
21 * There would also need to be an interface presented to the task for pause and resume. Resuming is the harder one,
22 * because the task needs an object to call when 'Long_Task.prototype.run' is no t in the call stack. Currently, the
23 * generator is instantiated before the constructor for this class, which means either passing such an object with
24 * 'send()' if that constructor is to be called. The better way is likely for th is class to instantiate the generator
25 * with a pause/resume object as argument, but this only allows parametric gener ators (pretty much a requirement) if
26 * if 'Function.protoype.bind()' works on generator-functions (untried) or if an equivalent can be hacked up.
27 *
28 * @param {Generator} task_generator
29 * The task generator is the task to be run, implemented as a generator. Ea ch call to the generator performs an
30 * increment of computation, whose size is determined by the task. This is part of a cooperative multitasking
31 * system.
32 * <p/>
33 * Note that this argument is a generator, not the function that returns a generator when called. Instantiating
34 * the generator with a function call is the responsibility of the code tha t instantiates this class.
35 * <p/>
36 * Note 'task_generator.next()' is always called at least once, because 'ru n()' calls that method before it detects
37 * cancellation. This is required by the interface to a generator, since it 's valid to call 'send()', which is how
38 * the runner signals cancellation to the task, only after the first call t o 'next'. If, for whatever reason, it's
39 * necessary to detect cancellation before the work begins, the generator s hould have an extra do-nothing 'yield'
40 * statement at the beginning.
41 *
42 * @param {Boolean} [may_pause=false]
43 * The
44 *
45 * @param {Number} [runaway_limit]
46 * The maximum number of iterations before 'run()' will throw an exception. Disable runaway detection by
47 * setting this value to zero.
48 *
49 */
50 var Long_Task = exports.Long_Task = function( task_instance, may_pause, runaway_ limit )
51 {
52 /**
53 * The core of a long task is a generator that runs on unit of computation w ith each call to next(). This object
54 * will give us such a generator by calling its generator() member.
55 * @type {*}
56 */
57 this.task_instance = task_instance;
58
59 /**
60 * The task generator for the task. It's initialized to null here, and set t o the actual generator at the beginning
61 * of the run() method.
62 * <p/>
63 * It returns 'false' when it is not yet completed and 'true' once it has. C alling the generator with 'send( true )'
64 * notifies the generator that it has been cancelled; thereafter it must ret urn 'true' always.
65 * @type {Boolean}
66 */
67 this.task_generator = null;
68
69 /**
70 * Cancellation flag. Set in the cancel() method. Tested each iteration in r un().
71 * @type {Boolean}
72 */
73 this.cancelled = false;
74
75 /**
76 * Pause state flag.
77 * @type {Boolean}
78 */
79 this.paused = false;
80
81 /**
82 * Runnable flag. This is essentially the state variable of a two-state mach ine, which starts at "runnable" and
83 * goes to "completed".
84 * @type {Boolean}
85 */
86 this.runnable = true;
87
88 /**
89 * The maximum number of iterations before 'run()' will throw an exception. Disable runaway detection by setting
90 * this value to zero.
91 * @type {Number}
92 */
93 this.runaway_limit = (arguments.length < 2) ? default_runaway_limit : runawa y_limit;
94
95 /**
96 * Iteration counter. This is only incremented when a runaway limit is in ef fect.
97 * @type {Number}
98 */
99 this.count = 0;
100
101 /**
102 * XPCOM thread manager. Used to implement dispatch().
103 * @type {nsIThreadManager}
104 */
105 this.thread_manager = Cc["@mozilla.org/thread-manager;1"].createInstance( Ci .nsIThreadManager );
106
107 /**
108 * Logging service.
109 * @type {Logger}
110 */
111 this.logger = new Logger( "Long_Task" );
112 };
113
114 /**
115 * Close the task out completely.
116 */
117 Long_Task.prototype.close = function()
118 {
119 this.cancel();
120
121 // DEFECT: We need to close the iterator, as well.
122 /*
123 * This is not trivial to implement correctly. If the task is paused, it mea ns there's a pending operation that we
124 * cannot prevent from executing, but will at some point will call resume(). It's also possible that there's already
125 * another iteration of the main loop already dispatched.
126 */
127 };
128
129 /**
130 * Cancel command. Calling this function cancels the pending task as soon as pos sible, which is nowhere near
131 * immediate with JavaScript.
132 * <p/>
133 * WARNING: The current way that cancellation is implemented, there will be one additional call to the task
134 * generator before cancellation. If that's a problem, it's time to fix the algo rithm, which means making
135 * an initial call to 'next()' before setting up the standing loop, and swapping the order of iterating and checking
136 * for cancellation.
137 */
138 Long_Task.prototype.cancel = function()
139 {
140 this.cancelled = true;
141 };
142
143 /**
144 * Run command
145 */
146 Long_Task.prototype.run = function()
147 {
148 var log = this.logger.make_log( "run" );
149 log( "Begin, runaway_limit = " + this.runaway_limit );
150
151 if ( !this.runnable )
152 {
153 throw "Long_Task no longer runnable";
154 }
155 this.task_generator = this.task_instance.generator( this.pause.bind( this ), this.resume.bind( this ) );
156 this._run_once();
157 };
158
159 /**
160 * The main body of the runner.
161 */
162 Long_Task.prototype._run_once = function()
163 {
164 var log = this.logger.make_log( "_run_once" );
165
166 /*
167 * The bulk of this function is structure as 'do ... while( false )' in orde r to use 'break' for flow control,
168 * instead of splitting off a second function and using 'return'.
169 */
170 //noinspection LoopStatementThatDoesntLoopJS
171 do
172 {
173 /*
174 * If we must pause, we simply don't do anything else now. The landing c ode of the asynchronous object must
175 * call resume(), which will dispatch the present function again and sta rt the iteration cycle up again.
176 */
177 if ( this.paused )
178 {
179 return;
180 /*
181 * FUTURE: start a watchdog timer here that will cancel the object i f the task times out.
182 */
183 }
184
185 /*
186 * Main iteration call. The call to run() goes into a try-block to ensur e we stop gracefully if the generator
187 * throws, since that doesn't always signal an error
188 */
189 try
190 {
191 if ( this.task_generator.next() )
192 {
193 /*
194 * The task generator returned true, which means that it's finis hed.
195 */
196 break;
197 }
198 }
199 catch ( ex )
200 {
201 if ( ex === StopIteration )
202 {
203 log( "End. Task iterator stopped" );
204 /*
205 * StopIteration is not an error but just signals the end of dat a for an ordinary iterator. Since the
206 * generator has signalled us, we don't send any signal by calli ng 'send()'.
207 */
208 break;
209 }
210 else
211 {
212 log( "Iteration exception " + ex.toString() );
213 throw ex;
214 }
215 }
216
217 /*
218 * Runaway detection.
219 */
220 if ( this.runaway_limit > 0 )
221 {
222 ++this.count;
223 log( "Iteration " + this.count, false );
224 if ( this.count >= this.runaway_limit )
225 {
226 this.cancelled = true;
227 /*
228 * FUTURE: This should really throw an exception after cancellin g the generator.
229 */
230 }
231 }
232
233 /*
234 * Cancellation detection.
235 */
236 if ( this.cancelled )
237 {
238 log( "Cancellation begin" );
239 try
240 {
241 /*
242 * We've received a command to cancel from elsewhere. Notify the generator that we're shutting down and
243 * exit the loop. We're doing this within a try-block because th e generator will typically throw
244 * StopIteration at this point, which isn't an error.
245 */
246 this.task_generator.send( true );
247 }
248 catch ( ex )
249 {
250 /*
251 * StopIteration is not an error as a result of cancellation, bu t any other exception is.
252 */
253 if ( ex !== StopIteration )
254 {
255 log( "Cancellation exception: " + ex.toString() );
256 throw ex;
257 }
258 }
259 log( "Cancellation end" );
260 break;
261 }
262
263 /*
264 * Infinite loop behavior happens here, where we schedule ourselves for another run as soon as possible
265 * after we complete. This uses the container's thread manager, so it ex ecutes more-or-less immediately.
266 * If there are long-duration asynchronous actions in the task, such as loading web pages or AJAX calls,
267 * this routine runs too fast to be effective as a poll. Such tasks shou ld pause when such operations are
268 * pending.
269 */
270 this.dispatch();
271 return;
272 } while ( false );
273 this.runnable = false;
274 };
275
276 /**
277 * Pause instruction. Since JavaScript is not multi-threaded, the pause instruct ion does not block. Instead, it takes
278 * effect at the next 'yield' statement.
279 */
280 Long_Task.prototype.pause = function()
281 {
282 this.paused = true;
283 };
284
285 /**
286 *
287 */
288 Long_Task.prototype.resume = function()
289 {
290 this.paused = false;
291 this.dispatch();
292 };
293
294 /**
295 * Dispatch another iteration. This is used ordinarily at the end of _run_once() and also by resume() to restart
296 * the iteration.
297 */
298 Long_Task.prototype.dispatch = function()
299 {
300 this.thread_manager.currentThread.dispatch(
301 {run: this._run_once.bind( this )},
302 Ci.nsIEventTarget.DISPATCH_NORMAL
303 );
304 };
305
OLDNEW
« .hgignore ('K') | « lib/storage.js ('k') | lib/yaml.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld