
Libdill: Structured Concurrency for C (2016) - jgrahamc
http://libdill.org/index.html
======
majke
Ok, I'll chime in here since all the comments are missing the point.

If you are excited by golang-style in C, then Mr Sustrik's libmill is for you
[http://libmill.org/tutorial.html](http://libmill.org/tutorial.html)

LibDill is a thin experiment "above" of libmill. The main problem it tries to
solve is "cancellations". For the uninitiated, in normal programming languages
there is no way to cancel/interrupt a tread/coroutine from outside. Look at
the mess in pthread_cancel(3) [1]. In golang as well, there is no way to
cancel a coroutine.

Why this is important? Well, read Mr Sustrik's notes [2]. But basically -
imagine you want to define the time limit for a completion of some task. Or -
imagine what should happen if a HTTP request comes, initiates some actions
(SQL quieries?) and then disconnects. Normal programming languages have _NO_
way of expressing that - "client went away, stop processing its sql queries".

Libdill is an attempt to solve this. I must admit, I'm not fully on board with
the "structured concurrency" train of thought [3] , but the whole prospect of
defining semantics of killing/cancelling coroutines is absolutely amazing.

Also, review the golang "context" mess, which AFAIU tries to work around the
same problem. [4]

[1] [http://man7.org/linux/man-
pages/man3/pthread_cancel.3.html](http://man7.org/linux/man-
pages/man3/pthread_cancel.3.html) [2]
[http://250bpm.com/blog:71](http://250bpm.com/blog:71) [3]
[http://libdill.org/structured-
concurrency.html](http://libdill.org/structured-concurrency.html) [4]
[https://golang.org/pkg/context](https://golang.org/pkg/context)

~~~
ewrcoffee
Not sure why don't you like go's context model. It works pretty well in our
production system. I can see people would like to pass the context implicitly.
But I don't have a strong opinion. Explicitness is not that bad.

~~~
lobster_johnson
Context is problematic because it infects everything. If a function gains
context support, every call to it has to be updated, and if you're going to do
it right (and not just use context.Background()), every function calling
_those_ functions need to be updated, and so on. And the context has to be
passed downwards, too. It's turtles all the way up and down.

There's been talk of adding context to io.Reader and io.Writer, which would of
course affect pretty much every single Go codebase. For backwards
compatibility/convenience you therefore see a lot of APIs with complementary
functions Foo and FooCtx, one which takes a context and one that doesn't.
That's awkward, and even less elegant when interfaces are involved.

And the network packages (sockets, etc.), of course, have a parallel, non-
complementary mechanism to provide timeouts and cancelation that doesn't use
contexts. (The key/value pair context stuff is also unfortunate, in my
opinion. It's not typesafe, and results in awkward APIs.)

All of this happened because goroutines, as designed, cannot be forcibly
terminated. I don't know what the better solution is. I'm not sure why the
runtime cannot provoke a panic at the next context switch, but I bet there's a
good technical reason. It's unfortunate.

But it's not unreasonable that a large part of any codebase needs "contexts",
and so if everyone wants it, why not make it mandatory? Make it an implicit
argument threaded through every function call, a bit like "this" in languages
such as C++ and Java. The compiler could optimize it away from those codepaths
that are guaranteed not to need it. Go has a lot of built-in generic magic,
adding some global helpers to set a scope's context wouldn't be too bad.

The other route is to imitate Erlang's processes, which have their own heap,
and so killing a process doesn't leave cruft behind. Given how goroutines are
allowed to share memory, that's not likely to happen.

~~~
ewrcoffee
I see. Now I see a strong point of favoring implicitly passing the context -
attaching the context to a go routine local variable (no such thing yet), so
the aspect of context passing can be decoupled. Of course for some logic we
probably still want to do some explicit context stitching, but that won't
pollute most of interfaces. Would that address your concern?

------
jchw
>There is currently no support for Windows. Cygwin is very broken. It doesn't
support AF_UNIX properly, and so no further development will be done for this
platform.

Boy do I have good news for whoever wrote this:

[https://blogs.msdn.microsoft.com/commandline/2017/12/19/af_u...](https://blogs.msdn.microsoft.com/commandline/2017/12/19/af_unix-
comes-to-windows/)

~~~
TimMurnaghan
Doesn't sound great if a coroutine library has resorted to passing messages
across sockets. I was expecting something that implemented multiple stacks and
some abstraction of some synchronization.

~~~
pjc50
It appears to do that:
[https://github.com/sustrik/libdill/blob/master/stack.c](https://github.com/sustrik/libdill/blob/master/stack.c)

~~~
rumcajz
Correct. It does that. The IPC, on the other hand, is there for you to use if
you want to talk to other processes on the same machine.

------
luckydude
I really liked the Linda concept for concurrency but it doesn't seem to have
caught on in general. It isn't the same as what is being presented here but it
side steps the threads issues, the multiple cpu issues, all that falls out for
free. Seemed pretty cool and I suppose you could layer on cancellations (but I
suspect it would be a mess like pthread_cancel()).

[https://en.wikipedia.org/wiki/Linda_(coordination_language)](https://en.wikipedia.org/wiki/Linda_\(coordination_language\))

[http://linuxtuples.sourceforge.net/](http://linuxtuples.sourceforge.net/)

Anyone else like that stuff? Or have any insight why it isn't more widely
used? Maybe it's because it was a commercial thing?

------
kaeluka
I was confused -- how can a library add a keyword like "coroutine" to C. It
didn't look like macros were powerful enough to do that (I expected to at
least some context having to be passed to the worker, or so).

It turns out that a coroutine is just a function that the compiler should not
inline:
[https://github.com/sustrik/libmill/blob/master/libmill.h#L24...](https://github.com/sustrik/libmill/blob/master/libmill.h#L242)

~~~
rumcajz
Yes, it's just a normal function. What go() macro does is that it switches the
stack, then lets compiler-generated code put the args of the function at the
top of the new stack. Obviously, if the function was inlined, the above would
not work.

------
luckydude
The fact that this is all on one cpu seems like a problem, does it not?

Does someone have a mental model of how to take advantage of multiple cpus
without creating a mess? If so, can you explain it like I'm 5 (or more like
I'm 55 and tired)?

~~~
rumcajz
I think the environment is a bit different now than it was once.

You typically have a cluster of machines, so you have to be able to run
multiple instances of your application simultaneously to use all the
processing power. And once you can run mutliple instances of the application
in parallel you can as well run 16 instances of it on a 16-core box.

The upside is that you don't have two levels of granularity (threads,
machines) but only a single one (processes) which makes both the coding and
the ops as well as stuff like capacity planning much easier.

~~~
aidenn0
Even absent the cluster of machines I found this to be very useful. Several
years ago, I would use zeromq (thank you, btw) for communication and if the
socket overhead was found to be an issue, I would merge two processes and
switch from tcp:// to inproc://. Many "corrupt the world" type bugs are
isolated from each other by processes, and it made it very hard to
accidentally share state (the file system of course is a giant hunk of share
state, but you very rarely accidentally write to a file).

------
snarfy
I took a peek at the source briefly. There is a lot more going on than I was
expecting and was pleasantly surprised. I was expecting to find a simple
wrapper around setjmp/longjmp, but this is much more. Kudos.

------
JepZ
> coroutine void worker(const char *text) {

At first I read 'goroutine' and thought something is wrong with my mind and
that I have coded too much Go. But then I saw this:

> go(worker("Hello!"));

Now I am pretty sure someone got inspired by Golang ;-) Nice to see some
backporting of Go features to C.

Note: That doesn't mean that Go is the only language that supports easy
concurrent patterns, but that the creators of Go wanted to improve C and while
not everybody agrees that all they have done are improvements, making
concurrency easier most certainly is one.

~~~
scadge
I think it's fair to say that Go was hardly the first language to implement
the concept of coroutines[1]. Probably the only merit of Go here is the short
and convenient name of the 'run' method.

[1]
[https://en.wikipedia.org/wiki/Coroutine#Implementations](https://en.wikipedia.org/wiki/Coroutine#Implementations)

~~~
teamhappy

        > libmill was a project that aimed to copy Go's
        > concurrency model to C 1:1 [...]
    
        > libdill is a follow-up project that experiments with
        > structured concurrency and diverges from the Go model.
    

[http://libdill.org/documentation.html](http://libdill.org/documentation.html)

------
MobiusHorizons
Very nice!! I look forward to reading through all the structured concurrency
stuff later.

------
ausjke
so what is this different from pthread? how can I decide to pick one of them?

the new C11 has thread built-in but not used widely yet.

------
scadge
I read that NodeJS is written in C with the same concept in its core -
asynchronous on one core (thread) with interruptions. How does it compare to
libdill?

~~~
fleetfox
Node runs on top of libuv.
[https://nikhilm.github.io/uvbook/basics.html#hello-
world](https://nikhilm.github.io/uvbook/basics.html#hello-world)

~~~
scadge
So it looks like libuv _could use_ libdill, right?

~~~
beagle3
Could have, possibly, but makes no sense at all to retrofit libuv with libdill
at this point

~~~
piscisaureus
Shameless(-ish) plug:
[https://github.com/piscisaureus/wepoll](https://github.com/piscisaureus/wepoll)
is not a fat abstraction layer but it could help with windows support.

~~~
beagle3
Libuv supports windows and in some ways only exists because windows support is
needed; on Unix/Linux where Node started, libev was sufficient; libuv was
created to facilitate the windows port of node - though it is now standing in
its own right.

~~~
piscisaureus
I know - I am one of the authors of libuv. However libuv is pretty big and
imposes its own asynchronous i/o model onto your application.

Later I discovered that it is possible to do efficient epoll emulation on
windows so then I wrote wepoll. With it you can just stick to the good ol'
epoll/kqueue model and still support windows.

