Show HN: Moustique – C++14 coroutine-based non-blocking IO on Linux
github.com
github.com
Here’s why: https://stackoverflow.com/a/9376493/126995
You can also control this with the "max open file" ulimit setting. This is by default often 1024 - meaning your process can have max 1024 open file descriptors, and the max value of a descriptor will be 1023.
Even if you need to support one or two orders of magnitude more file descriptors, a vector would likely be more efficient due to better memory locality.
Vector’s memory locality only matters when you’re iterating over the elements. For this case it’s essentially a random index.
Besides, unordered_map memory locality is fixable: https://github.com/Const-me/CollectionMicrobench
See the man page at, e.g., http://man7.org/linux/man-pages/man2/socket.2.html from which the quote above is taken. Note that I'm not expressing any opinion on whether to use a vector or an unordered_map in this particular application.
Not “etc.”, accept(2) that creates the sockets in that code doesn’t guarantee that.
See the man page at, e.g., http://man7.org/linux/man-pages/man2/accept.2.html
"2.14. File Descriptor Allocation
All functions that open one or more file descriptors shall, unless specified otherwise, atomically allocate the lowest numbered available (that is, not already open in the calling process) file descriptor at the time of each allocation. Where a single function allocates two file descriptors (for example, pipe() or socketpair()), the allocations may be independent and therefore applications should not expect them to have adjacent values or depend on which has the higher value."
Multithreading.
To return lowest-numbered file descriptor, calls to that single function need to be serialized. On multi-core, and especially on NUMA, I can see how that might hit the performance.
Apps don’t normally create thousands of files, or pipes, or client sockets per second. However, for some server software, accepting thousands of sockets per second is normal.
Update, see also multi-queue NICs: https://www.kernel.org/doc/Documentation/networking/scaling....
boost asio already works with boost coroutines in a reasonable fashion. And this would certainly be a good way if someone wants to build some production project on top of it.
Regarding the code and example itself:
Having a separate callback for connection close seems to be a bit backward, since it again means the connection state is spread out over multiple callbacks. The purpose of a coroutine is to have everything in scope of that routine. In that case the connection close should be signaled by reading 0 from the socket, or the read returning an error.
I took your remark for the closing callback. read now returns 0 and write returns false when the connection is closed or lost.
template <typename G, typename H> int moustique_listen(int listen_fd, G closed_connection_handler, H data_handler);
Great project by the way - I've put it on my workbench-TODO for a little hacking some time in the next few days. I've got a standard 'command processing server' project that I've been hacking on over the years, I might try to hack up a moustique_ integration, just for grins.
int moustique_listen(const char* port, G closed_connection_handler, H data_handler);
int moustique_listen(int listen_fd, G closed_connection_handler, H data_handler);
This could be improved, because the port argument could easily be confused with the file argument.
I.e.: moustique_listen(80, ...)
What, no love for namespaces?
I often use namespaces but I do think that they can have a bad impact on the usability of the api, especially when you have too many deeply nested namespaces, and have to use plenty on using namespace ... to fix this.