UNPKG

14.5 kBMarkdownView Raw
1l8 0.1.60
2=========
3
4[![Build Status](https://travis-ci.org/JeanHuguesRobert/l8.png)](https://travis-ci.org/JeanHuguesRobert/l8)
5
6l8 is a modern multi-tasker for javascript. It schedules javascript tasks using
7promises and distributed actors. Such tasks can run browser side or server side.
8
9This is a work in progress that is not ready for production yet.
10See [![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```
14npm install l8
15cd node_modules/l8; npm test
16```
17
18A 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
22l8.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
44What is it?
45===========
46
47This 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
49l8 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
51Hence 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
53l8 tasks are also "promises". Once a task is completed, it's promise is either
54fullfilled or rejected depending on the task success or failure.
55
56The 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
58The "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
60Roadmap (feb 2013)
61==================
62
63Tasks - this is mostly done. Some more tests are needed.
64
65Node.js adaptor - it's about transforming all node.js API functions that use
66callbacks into l8 tasks to make it easier to use the node.js API in a blocking
67manner. See the test/node.js working example.
68
69Actors - local & proxied. This is mostly done, but recent. Needs more tests.
70
71Browser adaptor - this is starting to work. It's about running code on the
72browser using the exact same API as the one when running on a server, including
73the full node.js API. Some APIs will be emulated locally when possible, the
74others are submitted to a server via proxies.
75
76The goal is to have a tool to build code that runs in browsers and servers,
77distributed using the actor model for inter process communications.
78
79API
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```
283Please find more documentation in [the wiki](../../wiki/FrontPage)
284