libdill
libdill copied to clipboard
Strange behaviour after forking process.
I've got the following simple program that, as is, works. However, if you uncomment either of the msleep calls you will get an error. I have tested this on macOS 10.12.3 using libdill version 1.6 from the tarball.
#include <unistd.h>
#include <stdio.h>
#include <libdill.h>
int main() {
int s[2];
int rc = ipc_pair(s);
if (rc < 0) perror("ipc_pair");
int pid = fork();
if (pid == 0) {
int foo;
//msleep(now() + 1000);
rc = brecv(s[1], &foo, sizeof(foo), -1);
if (rc == -1) perror("brecv");
printf("%d\n", foo);
exit(0);
}
//msleep(now() + 1000);
int foo = 1234;
rc = bsend(s[0], &foo, sizeof(foo), -1);
if (rc == -1) perror("bsend");
}
If the first msleep is uncommented I get:
Assert failed: nevs >= 0 (./kqueue.c.inc:270)
If the second msleep is uncommented I get:
brecv: Bad file descriptor
Running unedited I get the expected: 1234
There used to be an equivalent of go() that forked a new process, e.g. proc(fn(1, 2, 3));
However, it's not clear what the semantics of such function should be. The original implementation killed all the coroutines in the forked process except for the running one and closed the current pollset. However, there's probably still a lot of allocated memory to free, file descriptors to close etc.
All in all, is there much reason for doing anything except exec() after the fork?
My use case was at the beginning of the program fork off a pool of worker processes that I could offload slow/blocking tasks to (filesystem I/O) so that it wouldn't slow down the main server process. This could be accomplished by compiling the worker as a separate binary and calling exec but it was not my first instinct to code it that way.
Also it leaves me a little confused as to the purpose of ipc_pair at first glance it seems analogous to pipe for which forking without exec is it's bread and butter. If you can't do that with ipc_pair is it even possible to to use ipc_pair to communicate between different processes? If not, why would you ever use it instead of a channel?
I am not against providing some kind of forking API, I am just genuinely not sure what the semantics should be. Maybe just killing all the threads but the running one, stopping polling on all file descriptors and letting all the other resources lay around non-deallocated would be OK. That seems to be what pthreads do after all.
As for syntax, would proc(fn(1, 2, 3)) work or do we need 1:1 equivalent of the fork() function? The latter is easier to use but maybe I am missing some important use case.
As for ipc, it's AF_UNIX sockets, i.e. you can create the connection even without fork, by referring to the same ipc file.
I think what you're describing would work for my case. I'm imagining something like this:
int worker_handles[NUM_WORKERS];
for (int i = 0; i < NUM_WORKERS; i++) {
int s[2];
ipc_pair(s);
worker_handles[i] = s[0];
proc(worker(s[1]));
}
Although if you stop polling all descriptors in the new process you probably wouldn't be able to just pass a handle into the new process like I did above and expect it to work. Maybe there could be a way to pass ownership of a handle? If not, using ipc_listen, etc. would still work.
Ok, sounds reasonable. I'll try to dig out the old code and hack something together.