Skip to content

Commit ccd1022

Browse files
committed
experiment
1 parent 41b2cfa commit ccd1022

File tree

2 files changed

+317
-6
lines changed

2 files changed

+317
-6
lines changed

corout.fdoc

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@tangler ex1 = examples/ex1.flx
22
@tangler ex2 = examples/ex2.flx
3+
@tangler ex3 = examples/ex3.flx
4+
@tangler ex4 = examples/ex4.flx
35
@title coroutines
46
@h1 A low level example.
57
I will show a simple low level example of coroutines, it will be a
@@ -207,7 +209,52 @@ Although this is not possible in Felix, you can write the option type @{opt} and
207209
the @{None} case to signal end of data. In fact, in Felix you can write <em>any</em>
208210
type down a channel, even channels!
209211

210-
@h1 Felix is a coroutine
212+
@h1 Asynchronous I/O
213+
Although coroutines are <em>synchronous</em> Felix supports an extension
214+
which allows two kinds of <em>asynchronous</em> I/O.
215+
216+
First, the library contains routines which can perform operations on sockets
217+
including @{read}, @{write}, @{bind}, and @{connect}. That subsystem was used
218+
to construct @{flx_web} which is the fastest web server in existence and can handle
219+
enormous loads. It outperforms other webservers because it associates a fibre,
220+
rather than a thread, with each connection, so context switching is lightning fast
221+
and occurs in user space.
222+
223+
The socket system also uses advanced socket state notificantion services such
224+
as @{kqueue} on MacOS and @{epoll} on Linux.
225+
226+
However we will demonstrate a simpler service, namely the system alarm closk
227+
because it has a really simply API.
228+
@tangle ex3
229+
println$ "Begin Spawning";
230+
231+
spawn_fthread {
232+
println "Start1"; sleep (2.0); println$ "End1";
233+
};
234+
235+
spawn_fthread {
236+
println "Start2"; sleep (1.0); println$ "End2";
237+
};
238+
239+
sleep (0.25);
240+
println$ "Mainline terminates";
241+
@
242+
And here's the output:
243+
@pre
244+
Begin Spawning
245+
Start1
246+
Start2
247+
Mainline terminates
248+
End2
249+
End1
250+
@
251+
252+
The @{sleep} procedure <em>suspends</em> the coroutine, it does not block
253+
the pthread running the coroutine system. So aftedr the first coroutine
254+
goes to sleep, the mainline continues and spawns the second coroutine.
255+
But then, the mainline itself completes and suicides by fall through.
256+
257+
@h2 Felix is a coroutine
211258
Most programming language generate programs which consist of single top level procedure,
212259
for example @{main()} in C. It is a subroutine called by the startup code which in turn
213260
was invoked by the operating system.
@@ -219,7 +266,111 @@ add a procedure @{flx_main()} to your code and that will also be run after
219266
the initialisation of the library.
220267

221268
However there is something else different: the Felix mainline code is not
222-
a subroutine, it is a coroutine!
269+
a subroutine, it is a coroutine! The mainline can happily terminate and
270+
leave other fibres running: there's really nothing special about your
271+
main program code, it's just another coroutine.
272+
273+
The Felix startup code creates a scheduler to run coroutines, and your
274+
program code is just the initial coroutine. So your program terminates
275+
when there is nothing left to do precisely because it's just another
276+
coroutine.
277+
278+
@h2 Running subsystems
279+
So far we have worked with the main scheduler, which terminates when there
280+
is nothing left to do. So I want to show you a little problem and
281+
how to solve it:
282+
@tangle ex4
283+
var x = list[int] (1,2,3,4,5);
284+
fun addup (acc: int) (elt:int) => acc + elt;
285+
var y = fold_left addup 0 x;
286+
println$ y;
287+
@
288+
This is the purely functional way to add up a list. It is a client server application
289+
where the fold operation is the service provider and the @{addup}
290+
function is the client. However the technical implementation has a severe
291+
problem: the client if forced to write a function to do the work, which can
292+
only handle one element at a time, and has a single state variable, @{acc},
293+
the accumulator. In other words, the client has to provide a callback,
294+
which is a slave to master fold routine.
295+
296+
Here is another way to do the same thing:
297+
@tangle ex4
298+
var acc = 0;
299+
for elt in x do
300+
acc = acc + elt;
301+
done
302+
@
303+
In this case we're using an iterator over the list which is provided
304+
by the library, and that iterator is a slave to the client code
305+
which is master: the iterator is called by the client.
306+
307+
Both these solutions work, and they are in fact dual or <em>control inverse</em>:
308+
each one is the other one turned inside out with respect to control relations.
309+
The client is a slave in the first solution whilst the iterator is a slave
310+
in the second.
311+
312+
There is way to do this without slavery .. using coroutines of course!
313+
@tangle ex4
314+
begin
315+
chip adder connector io pin inp: %<int {
316+
var acc = 0;
317+
while true do
318+
var elt = read io.inp;
319+
acc = acc + elt;
320+
println$ acc;
321+
done
322+
}
323+
(source_from_list x |-> adder)();
324+
end
325+
@
326+
The chip @{source_from_list} comes from the standard library, it just writes
327+
each element of a list down its output channel.
328+
329+
The code terminates when you expect and the last result printed is the answer.
330+
But we just want the answer!
331+
332+
So here is our first attempt:
333+
@tangle ex4
334+
begin
335+
var acc = 0;
336+
chip adder connector io pin inp: %<int {
337+
while true do
338+
sleep (0.025);
339+
var elt = read io.inp;
340+
acc = acc + elt;
341+
done
342+
}
343+
344+
(source_from_list x |-> adder)();
345+
println$ "Attempt1: " + acc.str;
346+
end
347+
@
348+
Of course this doesn't work we get:
349+
@pre
350+
Attempt1: 0
351+
@
352+
because as we know, the mainline is a coroutine, and it continues on whilst
353+
the @{adder} is sleeping. In the abstract semantics when there is a set
354+
of coroutines waiting to run, the scheduler can run <em>any one</em> of them
355+
so even without the sleep, it could complete the mainline before completing
356+
the pipeline.
357+
358+
359+
@tangle ex4
360+
begin
361+
var acc = 0;
362+
chip adder connector io pin inp: %<int {
363+
while true do
364+
var elt = read io.inp;
365+
acc = acc + elt;
366+
done
367+
}
368+
369+
run (source_from_list x |-> adder);
370+
println$ "Attempt2: " + acc.str;
371+
end
372+
@
373+
223374

224375

225376
@h1 The concept of fibration

0 commit comments

Comments
 (0)