Creating new processes

Processes are the main isolation unit in a multitasking operating system. Separate program instances run in separate processes, giving each one an isolated memory space and execution context which can never accidentally step on the toes of another.

Processes can also be a useful concept in the context of a single logical program. Creating a child process can allow you to use a separate program to help you complete your task or can isolate a subtask inside your program from the rest of it, giving you resiliency against crashes as well as concurrency and parallelization across several processors, even across several different physical computers in some cases.

How to do it…

Here, we'll briefly explore using processes for two tasks: creating crash-resistant plugins, which will work well across platforms, and forking execution to handle independent tasks, which uses Posix functions.

To create a crash-resistant plugin, we need to execute the following steps:

  1. Write the plugin as a separate program that communicates with the parent through the stdin and stdout streams.
  2. In the parent program, use the command import std.process;.
  3. Call pipeProcess to spawn the plug-in program.

To fork execution to handle independent tasks, we need to execute the following steps. These steps will not work in Windows.

  1. Use the command import core.sys.posix.unistd, core.sys.posix.sys.wait;.
  2. Call fork in an if statement.
  3. If fork returns zero, proceed with your task, terminating the process when you are finished.
  4. If fork returns nonzero, you are in the parent process. Perform your independent task, then wait for the child thread to finish before terminating.

The following is the code:

import core.sys.posix.unistd;
import core.sys.posix.sys.wait;

void main() {
  if(auto pid = fork()) {
    if(pid < 0) throw new Exception("fork failed");
    // this is the parent, continue doing
    // work here
    int statusCode;
    wait(&statusCode); // wait for the child to finish
  } else {
    // this is the child, it can do independent work
    // here
  }
}

How it works…

Crash-resistant plugins are separate programs that adhere to a communication protocol. Since they are separate processes and have totally isolated memory spaces and execution flows, a bug in the plugin will not be able to overwrite the memory or otherwise crash into the parent program.

The fork function is the main multitasking function in the Unix family of operating systems. When it runs, the process immediately splits into two: parent and child. Each is fully independent after the call, using separate copy-on-write memory spaces and execution contexts.

Tip

The fork function is not efficiently implemented on Windows, so it should not be used on that operating system. Instead, use threads and minimize shared data to get the same result.

The return value of fork tells you if you are in the parent or the child process. If it returns 0, you are in the child branch. If it returns a negative number, an error has occurred, and if it returns a positive number, you are in the parent branch and the return value is the ID of the new process. The processes execute independently, but the parent can keep track of the child's progress and wait for its termination. The child process will inherit data and open files from its parent, allowing them to communicate.

There's more…

Using reflection and code generation, we can automate most of the communication process between the two cooperating programs. Serializing and deserializing data can be automated with the reflection facilities, such as tupleof, and the messages may be represented by the functions' names (like we did in the command-line function caller in Chapter 8, Reflection) or numbers (using facilities such as __traits(getVirtualIndex)), called automatically with __traits(getMember).

Memory can be shared across processes using the operating system functions, such as shmget on Posix located in core.sys.posix.sys.shm. These functions work the same way in D as they do in C; however, ensure that the shared resources are properly released upon abnormal program termination. Using a destructor alone is not enough—you must also install signal handlers to ensure that the program is not terminated without giving your destructors a chance to run.

Use the sigaction function from core.sys.posix.signal to install signal handlers for any signal that can terminate the process, such as SIGINT and SIGPIPE. In the signal handler, set a flag or send a message for your event loop, which should reply with a normal return or by throwing an exception. If the program is terminated by a signal, the scoped cleanup functions will not run, but when an exception is thrown, all cleanup code is run before the program ends.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.44.182