1 | l8 0.1.60
|
2 | =========
|
3 |
|
4 | [![Build Status](https://travis-ci.org/JeanHuguesRobert/l8.png)](https://travis-ci.org/JeanHuguesRobert/l8)
|
5 |
|
6 | l8 is a modern multi-tasker for javascript. It schedules javascript tasks using
|
7 | promises and distributed actors. Such tasks can run browser side or server side.
|
8 |
|
9 | This is a work in progress that is not ready for production yet.
|
10 | See [![Build Status](https://c9.io/site/wp-content/themes/cloud9/img/logo_cloud9_small.png)](https://c9.io/jhr/l8)
|
11 |
|
12 | [npm](https://npmjs.org/package/l8)
|
13 | ```
|
14 | npm install l8
|
15 | cd node_modules/l8; npm test
|
16 | ```
|
17 |
|
18 | A task is any activity that a "normal" javascript function cannot do because... javascript functions cannot block! Where functions provide results, tasks provide promises instead. To become tasks that can block, functions are broken into steps that the l8 scheduler executes.
|
19 |
|
20 | ```
|
21 | // Simpliest multi-user html game ever, best solution in log2 N guesses
|
22 | l8.task ->
|
23 | @repeat ->
|
24 | round = random = 0
|
25 | @step -> input "Enter a decent number to start a new game"
|
26 | @step ( r ) ->
|
27 | @continue if ( r = parseInt( r, 10 ) ) < 10
|
28 | random = Math.floor Math.random() * r
|
29 | round = 0
|
30 | @repeat ->
|
31 | @step -> input "Guess a number"
|
32 | @step ( r ) ->
|
33 | round++
|
34 | r = parseInt( r, 10 )
|
35 | if r > random then printnl "#{r} is too big"
|
36 | if r < random then printnl "#{r} is too small"
|
37 | if r is random
|
38 | cls()
|
39 | printnl "Win in #{round} rounds! Try again"
|
40 | @break
|
41 | # extracted from test/input.coffee
|
42 | ```
|
43 |
|
44 | What is it?
|
45 | ===========
|
46 |
|
47 | This is a library to help those who want to embrace the javascript style of asynchronous programming but feel that the classic thread/blocking-function model is also very readable.
|
48 |
|
49 | l8 schedules the execution of multiple "tasks". A task is made of "steps", much like a function is made of statements. Execution goes from "step" to "step", steps are closures. If one cannot walk a step immediately, one does block, waiting for something before resuming. Steps can nest, like blocks of statements.
|
50 |
|
51 | Hence l8 tasks are kind of user level non preemptive threads. They are neither native threads, nor worker threads, nor fibers nor the result of some CPS transformation. Just a bunch of cooperating closures. However, if you are familiar with threads, l8 tasks should seem natural to you.
|
52 |
|
53 | l8 tasks are also "promises". Once a task is completed, it's promise is either
|
54 | fullfilled or rejected depending on the task success or failure.
|
55 |
|
56 | The main flow control structures are the sequential execution of steps, the execution and join of forked steps on parallel paths, error propagation similar to exception handling and synchronisation using the usual suspects (semaphores, mutexes, reentrant locks, message queues, ports, signals, generators...).
|
57 |
|
58 | The "thread" model of computation is not without shortcomings however. Race conditions and deadlocks are difficult to avoid when using the shared state paradigm. What is sometimes a necessary evil to gain maximal performance out of multiple cores cpus is not an option within a javascript process that is by design single threaded. This is why l8 favors a different approach based on message passing and distributed actors.
|
59 |
|
60 | Roadmap (feb 2013)
|
61 | ==================
|
62 |
|
63 | Tasks - this is mostly done. Some more tests are needed.
|
64 |
|
65 | Node.js adaptor - it's about transforming all node.js API functions that use
|
66 | callbacks into l8 tasks to make it easier to use the node.js API in a blocking
|
67 | manner. See the test/node.js working example.
|
68 |
|
69 | Actors - local & proxied. This is mostly done, but recent. Needs more tests.
|
70 |
|
71 | Browser adaptor - this is starting to work. It's about running code on the
|
72 | browser using the exact same API as the one when running on a server, including
|
73 | the full node.js API. Some APIs will be emulated locally when possible, the
|
74 | others are submitted to a server via proxies.
|
75 |
|
76 | The goal is to have a tool to build code that runs in browsers and servers,
|
77 | distributed using the actor model for inter process communications.
|
78 |
|
79 | API
|
80 | ===
|
81 |
|
82 | ```
|
83 | l8
|
84 | -- step/task creation. "body" can create additional steps/subtasks
|
85 | .step( body ) -- queue a step on the path to task's completion
|
86 | .task( body ) -- queue a step that waits on a blocking subtask
|
87 | .fork( body ) -- queue a step that starts a forked task, forks "join"
|
88 | .repeat( body ) -- queue a step that repeats a blocking subtask
|
89 | .spawn( body ) -- like fork() but next step does not wait for subtask
|
90 | .generate( body ) -- queue a step that spawn a task that yields results
|
91 |
|
92 | -- step walking
|
93 | .proceed( block ) -- walk a step on its path, at most once per step
|
94 | .walk -- idem but params of block become results of step
|
95 | .flow -- idem but first param is filtered out unless thrown
|
96 | .continue -- stop executing current task, reschedule it instead
|
97 | .break -- "break" for "repeat" steps
|
98 | .return( [val] ) -- like "return" in normal flow, skip all queued steps
|
99 | .raise( error ) -- raise an exception in task, skip all queued steps
|
100 |
|
101 | -- task completion monitoring, for task users
|
102 | .then( ... ) -- Promise/A protocol, tasks are promises
|
103 | .callback( cb ) - - Node.js style callback. Also .callback( promise, cb)
|
104 | .join() -- pause task until all subtasks are done
|
105 |
|
106 | -- task completion handling, for task implementers
|
107 | .defer( body ) -- push a block to execute when task is almost done
|
108 | .progress( block ) -- block to run when a subtask is done or step walked
|
109 | .success( block ) -- block to run when task is done without error
|
110 | .failure( block ) -- block to run when task is done but with error
|
111 | .final( block ) -- block to run when task is all done (after .defer())
|
112 |
|
113 | -- task "local" variables, subtasks inherit them, a binding store them
|
114 | .var( name, val ) -- define a new variable in current task's binding
|
115 | .get( name ) -- get value of task local variable
|
116 | .set( name, val ) -- set value of task local variable
|
117 | .binding( name ) -- return binding where task local variable is stored
|
118 |
|
119 | -- task state related
|
120 | .state -- return state of task, I->[Run|Pause]*->Success/Fail
|
121 | .pause -- block task at step, waiting until task is resumed
|
122 | .paused -- return true if task was paused
|
123 | .resume -- resume execution of task paused at some step
|
124 | .running -- true if task not done nor paused
|
125 | .cancel -- cancel task & its sub tasks, brutal
|
126 | .canceled -- true if task failed because it was canceled
|
127 | .stop -- gentle cancel
|
128 | .stopping -- true after a gentle cancel, until task is done
|
129 | .stopped -- true if done task was gently canceled (gracefull)
|
130 | .done -- true if task done, else either running or paused
|
131 | .succeed -- true if task done without error
|
132 | .fail -- true if task done but with an error
|
133 | .error -- last raised error (ie last exception)
|
134 | .result -- result of last successful step
|
135 | .timeout( milli ) -- cancel task if it is not done on time
|
136 | .sleep( milli ) -- block on step for a while, then move to next step
|
137 | .wait( promise ) -- block task until some lock opens, promise agnostic
|
138 |
|
139 | -- misc, task hierarchy
|
140 | .current -- return current task
|
141 | .parent -- return parent task
|
142 | .tasks -- return immediate pending sub tasks
|
143 | .top -- return top task of sub task (child of l8 root task)
|
144 |
|
145 | -- scoping (value of "this" related)
|
146 | .begin -- create a new task
|
147 | .end -- start that new task
|
148 | .Task( function ) -- the .begin/.end guarded version of a function
|
149 |
|
150 | All these methods, if invoked against the global l8 object, will usually get
|
151 | forwarded to the "current task", the task that is currently executing. That
|
152 | task is often the returned value of such methods, when it makes sense. When
|
153 | the body of a task is executing, "this" references the current task.
|
154 |
|
155 | -- synchronization
|
156 |
|
157 | To synchronize the access to resources, l8 provide a few well known basic
|
158 | solutions implemented using promises and invoked using task.wait( resource ).
|
159 |
|
160 | .semaphore( [n] ) -- create a new semaphore, also a promise provider
|
161 | .mutex( [entered] ) -- ... a new mutex, also a ...
|
162 | .lock( [nentered] ) -- ... lock (reentrant mutex), ...
|
163 | .queue( [bound] ) -- message queue, ...
|
164 | .port() -- like a message queue but without any buffering
|
165 | .signal() -- signal, ..., like a promise that fires many times
|
166 | .timeout( delay ) -- a promise fulfilled within a delay
|
167 | .call( fn ) -- like callback but returns a promise when signaled
|
168 | .generate( block ) -- starts a next()/yield() consumer/producer generator
|
169 | .Generator( block ) -- build a Generator Constructor.
|
170 |
|
171 | Semaphores, Mutexes and Locks provide:
|
172 |
|
173 | .promise -- provide a promise fullfilled when rsrc is acquired
|
174 | .release() -- make resource available
|
175 | .signal() -- alias for release()
|
176 | .close() -- reject pending promises
|
177 | .task -- resource owner task, when applicable (mutex & lock)
|
178 |
|
179 | Message queues are useful to synchronize a consumer and a producer:
|
180 |
|
181 | .in -- a "can get()" promise,
|
182 | .promise -- alias for .in
|
183 | .out -- a "can put()" promise
|
184 | .get() -- pause current task until queue is not empty, get msg
|
185 | .try_get() -- get msg when one is available, don't block
|
186 | .put( msg ) -- pause current task until queue is not full, put msg
|
187 | .try_put( msg ) -- put msg in queue unless queue is full
|
188 | .signal( msg ) -- alias for try_put()
|
189 | .capacity -- total capacity (bound)
|
190 | .length -- used capacity
|
191 | .full -- when capacity is totally used
|
192 | .empty -- when length is 0
|
193 |
|
194 | Timeouts are convenient to measure time and detect excessive delays.
|
195 |
|
196 | .promise -- provide a promise fullfilled withing the delay
|
197 | .signal() -- fire the timeout now
|
198 | .started -- time when the timeout was started
|
199 | .signaled -- time when the timeout was signaled, or null
|
200 | .duration -- how long it took (took so far if unsignaled timeout)
|
201 |
|
202 | Signals are usefull to send a signal to multiple tasks when some condition is
|
203 | met:
|
204 |
|
205 | .promise -- a promise fullfilled when signal is next signaled
|
206 | .signal( value ) -- signal signal, resolve all pending promises
|
207 |
|
208 | Calls are functions that will be called when signaled. They are similar to
|
209 | regular callbacks. The main difference is that in addition to .apply() and
|
210 | .call(), Calls also provide a .signal() method, like all the other l8 objects
|
211 | that are usefull for synchronisation purposes. Another difference is the fact
|
212 | that Calls are asynchronous, their result is a promise.
|
213 |
|
214 | .promise -- provide the promise of the call.
|
215 | .call( ... ) -- invoke the call with parameters
|
216 | .apply( a ) -- idem but parameters are specified using an array
|
217 | .signal( ... ) -- alias for .apply()
|
218 |
|
219 | Generators let a producer and a consumer collaborate in a next()/yield() way:
|
220 |
|
221 | .get -- a "can next()" promise, alias for .promise
|
222 | .put -- a "can yield()" promise
|
223 | .next( [msg] ) -- pause task until producer yields, get/send a msg
|
224 | .yield( msg ) -- pause task until consumer calls .next(), get/send
|
225 | .try_next( [msg] ) -- if .get promise is ready, get yield's msg
|
226 | .try_yield( msg ) -- if .put promise is ready, get next's msg
|
227 | .signal( msg ) -- alias for try_yield()
|
228 | .close() -- break paused tasks (using .break())
|
229 | .closed -- true once generator is closed
|
230 |
|
231 | When a producer task is created using a Generator Constructor, that task can
|
232 | use l8.yield() while the parent task can use l8.next() ; the associated
|
233 | generator will automatically get closed when either the producer or the
|
234 | consumer task terminates.
|
235 |
|
236 | Many actions are possible when you have a hand of promises, l8 provides some
|
237 | of them:
|
238 |
|
239 | .selector( promises ) -- fires when any promise does
|
240 | .any( promises ) -- alias for .selector()
|
241 | .or( promises ) -- fires when a promise with a non falsy result fires
|
242 | .aggregator( promises) -- collect results, fires when all promises did
|
243 | .all( promises ) -- alias for .aggregator()
|
244 | .and( promises ) -- fires with "false" early or with collected results
|
245 |
|
246 | Note: in addition to promises, the array can contain immediate values and
|
247 | functions returning either an immediate value, a function to evaluate or a
|
248 | promise. The result of a promise can be a Function that will be evaluated and
|
249 | will replace the initial promise.
|
250 |
|
251 | Additional librairies provides other usefull services. See Q.js, When.js,
|
252 | Promise.io, etc.
|
253 |
|
254 | -- Actors runs in places called "stages"
|
255 | They are remotely accessible using proxies.
|
256 |
|
257 | User:
|
258 | .actor( name, pattern ) -- start an actor or return an actor generator
|
259 | .actor( name ) -- look for an existing actor, local or remote
|
260 | .actor( name, http ) -- access to a remote actor
|
261 | .actor( name, stage ) -- access to browser side remote actors
|
262 | .tell( ... ) -- send a message to the actor
|
263 | .ask( ... ) -- send a message and expect an answer
|
264 |
|
265 | Implementer:
|
266 | .receive( pattern ) -- define actor reaction to received messages
|
267 | .ego -- actor the current task is running
|
268 | .ego.stage -- stage the actor received current message from
|
269 | .stage( name, [url] ) -- a place with actors in it
|
270 | .stage( "local", srv ) -- define http server for local stage
|
271 |
|
272 | -- Misc
|
273 |
|
274 | .debug( [on]) -- get/set debug mode
|
275 | .trace( p1, ... ) -- output trace
|
276 | .logger( f() ) -- command how function used to output traces is found
|
277 | .assert( cndtion ) -- bomb when condition is not met
|
278 | .de -- my de&&bug() darling
|
279 | .bug( ... ) -- alias for .trace()
|
280 | .mand( condition ) -- my de&&mand() darling, alias for .assert()
|
281 |
|
282 | ```
|
283 | Please find more documentation in [the wiki](../../wiki/FrontPage)
|
284 |
|