I am working on a C library (SlipRock) for interprocess communication.
The library currently exposes a simple, blocking API. This is easy to use, makes misuse (relatively) difficult (this is C after all), and will be (relatively) easy to implement cross-platform, once I get around to implementing the Windows port (which shares essentially no common code).
However, I am worried that the blocking API might cause problems in certain use cases. Nevertheless, I am very worried about providing a non-blocking API that is easy to use and doesn’t require the use of heavyweight libraries like libuv or libevent.
Bindings to languages that naturally do pseudo-blocking (blocks the green thread) IO (such as Go, Erlang, or GHC Haskell) can always just implement the higher-level API (or even the entire protocol – it isn’t that complex) in the higher level language. Similarly, it is possible to implement the parts of the protocol that are not by-nature blocking (listening on a named pipe/AF_UNIX socket and connecting to an AF_UNIX socket, as well as the following exchange of passwords) in the higher-level language.
Should I provide a non-blocking C API, or should I just provide the 2 or 3 functions thatbindings would need to use as the basis for the API?
The way I was thinking of implementing the non-blocking API was by having a state machine that the user is required to manually invoke. But that requires the user to have access to the underlying IO handle – which is the intended return value! So I either need to break encapsulation or tie myself to an event loop implementation if I want to expose an async API for this not-very-performance-critical code.
select(2)
orpoll(2)
(both portable to any POSIX.1 system, BTW) with a zero or infinite timeout depending on whether or not the user wants to block? If you're providing a non-blocking interface, then by definition you're leaving responsibility for how it's (mis)used to the callers.