www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - [WEKA] Calling fork() and D runtime

reply Jonathan Shamir <jonathan weka.io> writes:
Hey,

This is my first time writing in the D forums!

I have an application written in D that runs as a linux daemon 
(some python service script is in charge of running and 
daemonizing it).
This "agent" works similar to docker - a service that accepts 
commands and runs in the background, and starts our application 
container. The container is itself a daemon (ppid = 1) with 
unshared namespaces etc.

So, normally, implementing such an application would look 
something like:
1. Main "agent" process runs fork() to create a child process 
(child_1). All memory is copy-on-write.
2. Child_1 malloc()s a stack, and calls clone() to create yet 
another child (child_2), which will eventually become the 
container pid 1.
3. Child_2 initializes the container (mounts, unshare, chroot, 
etc) then eventually exec()s into the container init process.
4. child_1 exit()s, which causes child_2 to become a daemon.
5. The agent main process should wait() on the forked pid since 
it's impolite to leave zombies (I do this in a thread).

The problem I encounter is with the forked process (child_1).

Here is the code I wrote handling the fork() (Note: this 
functionality should really be provided by core.threads or 
something, for unix environments).

```private void deferToForkProcess(int delegate() entryPoint, 
Timeout timeout = Timeout.infinite) {
     import core.runtime : Runtime;
     import core.sys.posix.unistd : fork, pid_t;
     import core.sys.posix.sys.wait;
     import core.stdc.stdlib : exit;

     int rc = theReactor.deferToThread({
         pid_t pid = fork();
         errnoEnforce(pid >= 0, "Fork failed");

         // child process
         if (pid == 0) {
             try {
                 int rc = entryPoint();
                 exit(rc);
             } catch (Throwable ex) {
                 try {
                     LOG_ERROR(ex.toString);
                 } catch (Throwable) {}
                 exit(1);
             }
             //assert(false);
         }

         // parent - wait for child to exit

         int status = 0;
         do {
             errnoEnforce(waitpid(pid, &status, 0) != -1, "Waitpid 
failed");
         } while (!WIFEXITED(status));

         int rc = WEXITSTATUS(status);
         return rc;
     }, timeout);

     enforce(rc == 0, "Child process failed (rc = %d)".format(rc));
}
```

entryPoint() returns 0, but the exit(0) raises an 
OutOfMemoryError:
```0x4e6472
exit
??:0
0x4e6428
__run_exit_handlers
??:0
0x4df976
__libc_csu_fini
??:0
0x40327e
ldc.register_dso
crtstuff.c:0
0x4caee4
_d_dso_registry
??:0
0x4ccdba
_D2rt4util9container6common8xreallocFNbNiPvmZPv
??:0
0x4b873d
onOutOfMemoryError
??:0```

I tried to call Runtime.initialize() and Runtime.terminate() 
surrounding the call to entryPoint(), but this didn't help. I 
suspect calling initialize() was a no-op since the forked process 
shares (copy-on-write) the VM space with it's parent, that 
already initialized the runtime. (Note: confirmed, initialize() 
returns true indicating it was already inited).

What is the correct way to handle fork() with the D runtime?
May 15 2017
next sibling parent reply Daniel Kozak via Digitalmars-d <digitalmars-d puremagic.com> writes:
This is how I use fork on one of my project
http://gitlab.eurosat.cz/ekj/esatd/raw/master/source/app.d

On Mon, May 15, 2017 at 1:33 PM, Jonathan Shamir via Digitalmars-d <
digitalmars-d puremagic.com> wrote:

 Hey,

 This is my first time writing in the D forums!

 I have an application written in D that runs as a linux daemon (some
 python service script is in charge of running and daemonizing it).
 This "agent" works similar to docker - a service that accepts commands and
 runs in the background, and starts our application container. The container
 is itself a daemon (ppid = 1) with unshared namespaces etc.

 So, normally, implementing such an application would look something like:
 1. Main "agent" process runs fork() to create a child process (child_1).
 All memory is copy-on-write.
 2. Child_1 malloc()s a stack, and calls clone() to create yet another
 child (child_2), which will eventually become the container pid 1.
 3. Child_2 initializes the container (mounts, unshare, chroot, etc) then
 eventually exec()s into the container init process.
 4. child_1 exit()s, which causes child_2 to become a daemon.
 5. The agent main process should wait() on the forked pid since it's
 impolite to leave zombies (I do this in a thread).

 The problem I encounter is with the forked process (child_1).

 Here is the code I wrote handling the fork() (Note: this functionality
 should really be provided by core.threads or something, for unix
 environments).

 ```private void deferToForkProcess(int delegate() entryPoint, Timeout
 timeout = Timeout.infinite) {
     import core.runtime : Runtime;
     import core.sys.posix.unistd : fork, pid_t;
     import core.sys.posix.sys.wait;
     import core.stdc.stdlib : exit;

     int rc = theReactor.deferToThread({
         pid_t pid = fork();
         errnoEnforce(pid >= 0, "Fork failed");

         // child process
         if (pid == 0) {
             try {
                 int rc = entryPoint();
                 exit(rc);
             } catch (Throwable ex) {
                 try {
                     LOG_ERROR(ex.toString);
                 } catch (Throwable) {}
                 exit(1);
             }
             //assert(false);
         }

         // parent - wait for child to exit

         int status = 0;
         do {
             errnoEnforce(waitpid(pid, &status, 0) != -1, "Waitpid failed");
         } while (!WIFEXITED(status));

         int rc = WEXITSTATUS(status);
         return rc;
     }, timeout);

     enforce(rc == 0, "Child process failed (rc = %d)".format(rc));
 }
 ```

 entryPoint() returns 0, but the exit(0) raises an OutOfMemoryError:
 ```0x4e6472
 exit
 ??:0
 0x4e6428
 __run_exit_handlers
 ??:0
 0x4df976
 __libc_csu_fini
 ??:0
 0x40327e
 ldc.register_dso
 crtstuff.c:0
 0x4caee4
 _d_dso_registry
 ??:0
 0x4ccdba
 _D2rt4util9container6common8xreallocFNbNiPvmZPv
 ??:0
 0x4b873d
 onOutOfMemoryError
 ??:0```

 I tried to call Runtime.initialize() and Runtime.terminate() surrounding
 the call to entryPoint(), but this didn't help. I suspect calling
 initialize() was a no-op since the forked process shares (copy-on-write)
 the VM space with it's parent, that already initialized the runtime. (Note:
 confirmed, initialize() returns true indicating it was already inited).

 What is the correct way to handle fork() with the D runtime?
May 15 2017
parent Jonathan Shamir <jonathan weka.io> writes:
OK well, seems like calling _exit() (instead of exit()) did the 
trick. Thanks for the help!
May 15 2017
prev sibling next sibling parent Kagamin <spam here.lot> writes:
D runtime is incompatible with fork, enforce and catch won't work 
in the child process after fork, stick with betterC style.
May 16 2017
prev sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Monday, 15 May 2017 at 11:33:29 UTC, Jonathan Shamir wrote:
 Hey,

 This is my first time writing in the D forums!

 I have an application written in D that runs as a linux daemon 
 (some python service script is in charge of running and 
 daemonizing it).
 This "agent" works similar to docker - a service that accepts 
 commands and runs in the background, and starts our application 
 container. The container is itself a daemon (ppid = 1) with 
 unshared namespaces etc.

 So, normally, implementing such an application would look 
 something like:
 1. Main "agent" process runs fork() to create a child process 
 (child_1). All memory is copy-on-write.
 2. Child_1 malloc()s a stack, and calls clone() to create yet 
 another child (child_2), which will eventually become the 
 container pid 1.
 3. Child_2 initializes the container (mounts, unshare, chroot, 
 etc) then eventually exec()s into the container init process.
 4. child_1 exit()s, which causes child_2 to become a daemon.
 5. The agent main process should wait() on the forked pid since 
 it's impolite to leave zombies (I do this in a thread).

 The problem I encounter is with the forked process (child_1).

 Here is the code I wrote handling the fork() (Note: this 
 functionality should really be provided by core.threads or 
 something, for unix environments).

 ```private void deferToForkProcess(int delegate() entryPoint, 
 Timeout timeout = Timeout.infinite) {
     import core.runtime : Runtime;
     import core.sys.posix.unistd : fork, pid_t;
     import core.sys.posix.sys.wait;
     import core.stdc.stdlib : exit;

     int rc = theReactor.deferToThread({
         pid_t pid = fork();
         errnoEnforce(pid >= 0, "Fork failed");

         // child process
         if (pid == 0) {
             try {
                 int rc = entryPoint();
                 exit(rc);
             } catch (Throwable ex) {
                 try {
                     LOG_ERROR(ex.toString);
                 } catch (Throwable) {}
                 exit(1);
             }
             //assert(false);
         }

         // parent - wait for child to exit

         int status = 0;
         do {
             errnoEnforce(waitpid(pid, &status, 0) != -1, 
 "Waitpid failed");
         } while (!WIFEXITED(status));

         int rc = WEXITSTATUS(status);
         return rc;
     }, timeout);

     enforce(rc == 0, "Child process failed (rc = 
 %d)".format(rc));
 }
 ```

 entryPoint() returns 0, but the exit(0) raises an 
 OutOfMemoryError:
 ```0x4e6472
 exit
 ??:0
 0x4e6428
 __run_exit_handlers
 ??:0
 0x4df976
 __libc_csu_fini
 ??:0
 0x40327e
 ldc.register_dso
 crtstuff.c:0
 0x4caee4
 _d_dso_registry
 ??:0
 0x4ccdba
 _D2rt4util9container6common8xreallocFNbNiPvmZPv
 ??:0
 0x4b873d
 onOutOfMemoryError
 ??:0```

 I tried to call Runtime.initialize() and Runtime.terminate() 
 surrounding the call to entryPoint(), but this didn't help. I 
 suspect calling initialize() was a no-op since the forked 
 process shares (copy-on-write) the VM space with it's parent, 
 that already initialized the runtime. (Note: confirmed, 
 initialize() returns true indicating it was already inited).

 What is the correct way to handle fork() with the D runtime?
Some links to related discussions: https://issues.dlang.org/show_bug.cgi?id=14205 http://forum.dlang.org/thread/ksqubftqniwznqbmurrk forum.dlang.org https://issues.dlang.org/show_bug.cgi?id=14770 https://issues.dlang.org/show_bug.cgi?id=16006 https://github.com/dlang/phobos/pull/4294 https://github.com/dlang/druntime/pull/1569
May 21 2017