| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 /** | 
|  | 2  * The default value for runaway_limit, used only in the constructor. | 
|  | 3  * @type {Number} | 
|  | 4  */ | 
|  | 5 const default_runaway_limit = 1000; | 
|  | 6 | 
|  | 7 /** | 
|  | 8  * Class for long-running tasks. Such tasks divide work into discrete units of e
     ffort, which allows them to be | 
|  | 9  * interrupted and to post progress. | 
|  | 10  * <p/> | 
|  | 11  * Note that are no callback or events in this class. Any state feedback should 
     be implemented as needed within the | 
|  | 12  * task generator. task generator can distinguish cancellation by examining the 
     rvalue of its yield statements. The | 
|  | 13  * generator cannot detect being paused, but the control object that started the
      task can. | 
|  | 14  * <p/> | 
|  | 15  * PLANNED: There's no 'pause()' in this version. This is very similar to 'cance
     l()' except that it's restartable. The | 
|  | 16  * code isn't particularly difficult, but it would require reworking the main 'r
     un()' loop. | 
|  | 17  * <p/> | 
|  | 18  * PLANNED: The current version will handle AJAX calls by polling the task gener
     ator every millisecond to see if there | 
|  | 19  * have been pending calls that have completed since the last poll. It would mak
     e for better performance for the task | 
|  | 20  * to signal that it should pause execution and then continue only after a pendi
     ng call completes. This would eliminate | 
|  | 21  * run-time polling overhead. | 
|  | 22  * <p/> | 
|  | 23  * There would also need to be an interface presented to the task for pause and 
     resume. Resuming is the harder one, | 
|  | 24  * because the task needs an object to call when 'Long_Task.prototype.run' is no
     t in the call stack. Currently, the | 
|  | 25  * generator is instantiated before the constructor for this class, which means 
     either passing such an object with | 
|  | 26  * 'send()' if that constructor is to be called. The better way is likely for th
     is class to instantiate the generator | 
|  | 27  * with a pause/resume object as argument, but this only allows parametric gener
     ators (pretty much a requirement) if | 
|  | 28  * if 'Function.protoype.bind()' works on generator-functions (untried) or if an
      equivalent can be hacked up. | 
|  | 29  * | 
|  | 30  * @param {Generator} task_generator | 
|  | 31  *      The task generator is the task to be run, implemented as a generator. Ea
     ch call to the generator performs an | 
|  | 32  *      increment of computation, whose size is determined by the task. This is 
     part of a cooperative multitasking | 
|  | 33  *      system. | 
|  | 34  *      <p/> | 
|  | 35  *      Note that this argument is a generator, not the function that returns a 
     generator when called. Instantiating | 
|  | 36  *      the generator with a function call is the responsibility of the code tha
     t instantiates this class. | 
|  | 37  *      <p/> | 
|  | 38  *      Note 'task_generator.next()' is always called at least once, because 'ru
     n()' calls that method before it detects | 
|  | 39  *      cancellation. This is required by the interface to a generator, since it
     's valid to call 'send()', which is how | 
|  | 40  *      the runner signals cancellation to the task, only after the first call t
     o 'next'. If, for whatever reason, it's | 
|  | 41  *      necessary to detect cancellation before the work begins, the generator s
     hould have an extra do-nothing 'yield' | 
|  | 42  *      statement at the beginning. | 
|  | 43  * | 
|  | 44  * @param {Number} runaway_limit | 
|  | 45  *      Optional. The maximum number of iterations before 'run()' will throw an 
     exception. Disable runaway detection by | 
|  | 46  *      setting this value to zero. | 
|  | 47  * | 
|  | 48  */ | 
|  | 49 var Long_Task = exports.Long_Task = function ( task_generator, runaway_limit ) | 
|  | 50 { | 
|  | 51     /** | 
|  | 52      * The runner is a generator that performs a single increment of computation
      with each call to next(). It returns | 
|  | 53      * 'false' when it is not yet completed and 'true' once it has. Calling the 
     generator with 'send( true )' notifies | 
|  | 54      * the generator that it has been cancelled; thereafter it must return 'true
     ' always. | 
|  | 55      * @type {Function} | 
|  | 56      */ | 
|  | 57     this.task_generator = task_generator; | 
|  | 58 | 
|  | 59     /** | 
|  | 60      * Cancellation flag. Set in the cancel() method. Tested each iteration in r
     un(). | 
|  | 61      * @type {Boolean} | 
|  | 62      */ | 
|  | 63     this.cancelled = false; | 
|  | 64 | 
|  | 65     /** | 
|  | 66      * Runnable flag. This is essentially the state variable of a two-state mach
     ine, which starts at "runnable" and | 
|  | 67      * goes to "completed". | 
|  | 68      * @type {Boolean} | 
|  | 69      */ | 
|  | 70     this.runnable = true; | 
|  | 71 | 
|  | 72     /** | 
|  | 73      * The maximum number of iterations before 'run()' will throw an exception. 
     Disable runaway detection by setting | 
|  | 74      * this value to zero. | 
|  | 75      * @type {Number} | 
|  | 76      */ | 
|  | 77     this.runaway_limit = (arguments.length < 2) ? default_runaway_limit : runawa
     y_limit; | 
|  | 78 | 
|  | 79     /** | 
|  | 80      * Iteration counter. This is only incremented when a runaway limit is in ef
     fect. | 
|  | 81      * @type {Number} | 
|  | 82      */ | 
|  | 83     this.count = 0; | 
|  | 84 | 
|  | 85     /** | 
|  | 86      * Internal timer. This is an nsITimer rather than the window time to allow 
     background operation. | 
|  | 87      * @type {nsITimer} | 
|  | 88      */ | 
|  | 89     this.timer = Cc["@mozilla.org/timer;1"].createInstance( Ci.nsITimer ); | 
|  | 90 }; | 
|  | 91 | 
|  | 92 /** | 
|  | 93  * Cancel command. Calling this function cancels the pending task as soon as pos
     sible, which is nowhere near | 
|  | 94  * immediate with JavaScript. | 
|  | 95  * <p/> | 
|  | 96  * WARNING: The current way that cancellation is implemented, there will be one 
     additional call to the task | 
|  | 97  * generator before cancellation. If that's a problem, it's time to fix the algo
     rithm, which means making | 
|  | 98  * an initial call to 'next()' before setting up the standing loop, and swapping
      the order of iterating and checking | 
|  | 99  * for cancellation. | 
|  | 100  */ | 
|  | 101 Long_Task.prototype.cancel = function () | 
|  | 102 { | 
|  | 103     this.cancelled = true; | 
|  | 104 }; | 
|  | 105 | 
|  | 106 /** | 
|  | 107  * Run command | 
|  | 108  */ | 
|  | 109 Long_Task.prototype.run = function () | 
|  | 110 { | 
|  | 111     Long_Task.log( "run begin. runaway_limit = " + this.runaway_limit ); | 
|  | 112 | 
|  | 113     if ( !this.runnable ) | 
|  | 114     { | 
|  | 115         throw "Long_Task no longer runnable"; | 
|  | 116     } | 
|  | 117 | 
|  | 118     this._run_once(); | 
|  | 119 }; | 
|  | 120 | 
|  | 121 /** | 
|  | 122  * The main body of the runner. | 
|  | 123  */ | 
|  | 124 Long_Task.prototype._run_once = function () | 
|  | 125 { | 
|  | 126     /* | 
|  | 127      * The bulk of this function is structure as 'do ... while( false )' in orde
     r to use 'break' for flow control, | 
|  | 128      * instead of splitting off a second function and using 'return'. | 
|  | 129      */ | 
|  | 130     do | 
|  | 131     { | 
|  | 132         /* | 
|  | 133          * Main iteration call. The call to run() goes into a try-block to ensur
     e we stop gracefully if the generator | 
|  | 134          * throws, since that doesn't always signal an error | 
|  | 135          */ | 
|  | 136         try | 
|  | 137         { | 
|  | 138             if ( this.task_generator.next() ) | 
|  | 139             { | 
|  | 140                 /* | 
|  | 141                  * The task generator returned true, which means that it's finis
     hed. | 
|  | 142                  */ | 
|  | 143                 break; | 
|  | 144             } | 
|  | 145         } | 
|  | 146         catch ( ex ) | 
|  | 147         { | 
|  | 148             if ( ex === StopIteration ) | 
|  | 149             { | 
|  | 150                 Long_Task.log( "run iteration stopped" ); | 
|  | 151                 /* | 
|  | 152                  * StopIteration is not an error but just signals the end of dat
     a for an ordinary iterator. Since the | 
|  | 153                  * generator has signalled us, we don't send any signal by calli
     ng 'send()'. | 
|  | 154                  */ | 
|  | 155                 break; | 
|  | 156             } | 
|  | 157             else | 
|  | 158             { | 
|  | 159                 Long_Task.log( "run iteration exception: " + ex.toString() ); | 
|  | 160                 throw ex; | 
|  | 161             } | 
|  | 162         } | 
|  | 163 | 
|  | 164         /* | 
|  | 165          * Runaway detection. | 
|  | 166          */ | 
|  | 167         if ( this.runaway_limit > 0 ) | 
|  | 168         { | 
|  | 169             ++this.count; | 
|  | 170             Long_Task.log( "run iteration " + this.count, true ); | 
|  | 171             if ( this.count >= this.runaway_limit ) | 
|  | 172             { | 
|  | 173                 this.cancelled = true; | 
|  | 174                 /* | 
|  | 175                  * FUTURE: This should really throw an exception after cancellin
     g the generator. | 
|  | 176                  */ | 
|  | 177             } | 
|  | 178         } | 
|  | 179 | 
|  | 180         /* | 
|  | 181          * Cancellation detection. | 
|  | 182          */ | 
|  | 183         if ( this.cancelled ) | 
|  | 184         { | 
|  | 185             Long_Task.log( "run cancellation begin" ); | 
|  | 186             try | 
|  | 187             { | 
|  | 188                 /* | 
|  | 189                  * We've received a command to cancel from elsewhere. Notify the
      generator that we're shutting down and | 
|  | 190                  * exit the loop. We're doing this within a try-block because th
     e generator will typically throw | 
|  | 191                  * StopIteration at this point, which isn't an error. | 
|  | 192                  */ | 
|  | 193                 this.task_generator.send( true ); | 
|  | 194             } | 
|  | 195             catch ( ex ) | 
|  | 196             { | 
|  | 197                 /* | 
|  | 198                  * StopIteration is not an error as a result of cancellation, bu
     t any other exception is. | 
|  | 199                  */ | 
|  | 200                 if ( ex !== StopIteration ) | 
|  | 201                 { | 
|  | 202                     Long_Task.log( "run cancellation exception: " + ex.toString(
     ) ); | 
|  | 203                     throw ex; | 
|  | 204                 } | 
|  | 205             } | 
|  | 206             Long_Task.log( "run cancellation end" ); | 
|  | 207             break; | 
|  | 208         } | 
|  | 209 | 
|  | 210         /* | 
|  | 211          * Infinite loop behavior happens here, where we schedule ourselves for 
     another run as soon as possible | 
|  | 212          * after we complete. In practice, this means "at the next millisecond t
     imer tick, or after all event handlers | 
|  | 213          * have run, whichever comes last". If the unit of computation is less t
     han 50 ms, the duration overhead will be | 
|  | 214          * over 1%, but it's just fine for scheduling asynchronous events where 
     this internal timer is not the limiting | 
|  | 215          * factor in total duration, such as AJAX calls. | 
|  | 216          */ | 
|  | 217         this.timer.initWithCallback( this._run_once.bind( this ), 0, Ci.nsITimer
     .TYPE_ONE_SHOT ); | 
|  | 218         return; | 
|  | 219     } while ( false ) | 
|  | 220     this.runnable = false; | 
|  | 221 }; | 
|  | 222 | 
|  | 223 Long_Task.log = function ( msg, allow ) | 
|  | 224 { | 
|  | 225     if ( arguments.length < 2 || allow ) | 
|  | 226         Cu.reportError( "Long_Task: " + msg ); | 
|  | 227 }; | 
| OLD | NEW | 
|---|