To some extent rehashing points made in the commentary to the question, not excluding the chat session, here is an answer in several parts.
Terminology
It is generally best to reserve the term 'pipes' for the type of pipe created by the pipe()
system call (or function wrapping a system call), using the term FIFO for 'named pipes' created by the mkfifo()
system call. Note that you can't open pipes with the open()
system call; you can't open a FIFO except with open()
or its minor variant openat()
.
Also, see Is it a good idea to typedef pointers? to which the short answer is generally "No". It caused some confusion in your code, where you have:
if ((ret = write(parent, &orderA, sizeof(Order))) > 0)
if ((ret = read(parent, &order_A, sizeof(Order))) > 0)
In both cases, the variable is a pointer to an Order
(a node
), and the extra indirection of the &
is wrong because sizeof(Order *) != sizeof(Order)
— and you're sending the wrong data down the pipe, and reading to the wrong place. There would be less chance of confusion if you had typedef struct Order Order;
and the variables were of type Order *
.
Version 1
At one point in the discussion I sent the following (flawed) code. Surprisingly, it worked, more or less. But that was mostly by accident, not least because of of the read/write problems shown.
This code uses library function available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c
and stderr.h
in the src/libsoq sub-directory.
Defective code — do not use
/* SO 5396-9266 */
#include "posixver.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "stderr.h"
#define MAX_PROCESS 10
const char *defaultPipe = "/tmp/child";
static int orderNum = 0;
static int workerNum = 0;
static pid_t shutDown[MAX_PROCESS];
static char pipes[MAX_PROCESS][100];
typedef struct Order
{
int id;
char created[256];
char fullName[256];
char email[256];
char phone[256];
char status;
int performance;
int days;
struct Order *next;
} Order;
typedef struct Order *node;
void startJob(void);
void sendPriorityJobs(node priorityHead);
void handler(int signo, siginfo_t *info, void *context);
void createWorker(node orderA, int workerID);
node createNode(void);
node createOrder(char *fullName, char *email, char *phone, char *created, int performance);
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
err_setlogopts(ERR_PID|ERR_MILLI);
struct sigaction sa;
sa.sa_handler = (void *)handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction(SIGUSR2, &sa, NULL);
node test1 = createOrder("Jane Doe", "test1@gmail.com", "12345678", "2018-12-25 8:00", 1000);
node test2 = createOrder("John Doe", "test2@gmail.com", "87654321", "2018-12-25 9:00", 1001);
test1->next = test2;
printf("Parent pid: %d\n", getpid());
/*This is where we send the task */
sendPriorityJobs(test1);
for (int i = 0; i < workerNum; i++)
{
/*int p = open(pipes[i], O_RDONLY);
read(p, msgFromWorker, sizeof(msgFromWorker));
sleep(1);
printf("%s\n", msgFromWorker);
close(p);*/
int status;
int corpse = waitpid(shutDown[i], &status, 0);
if (corpse < 0)
err_sysrem("child %d - no status available: ", shutDown[i]);
else
err_remark("child %d (corpse %d) exited with status 0x%.4X\n", shutDown[i], corpse, status);
}
err_remark("All done!\n");
return 0;
}
static void dump_order(const char *tag, const node order)
{
err_remark("%s (%p):\n", tag, (void *)order);
err_remark("Order: %d, %s, %s, %s, %s, %d\n", order->id, order->fullName,
order->email, order->phone, order->created, order->performance);
}
void createWorker(node orderA, int workerID)
{
int parent; // child;
pid_t worker;
char strID[12];
sprintf(strID, "%d", workerID);
char pipe[100];
strcpy(pipe, defaultPipe);
strcat(pipe, strID);
if (mkfifo(pipe, S_IRUSR | S_IWUSR) != 0)
err_syserr("failed to create FIFO '%s': ", pipe);
err_remark("FIFO %s created\n", pipe);
worker = fork();
if (worker < 0)
err_syserr("failed to fork: ");
if (worker == 0)
{
err_remark("worker at play!\n");
//this is a temporarily variable for the received structure.(order)
node order_A = createNode();
parent = open(pipe, O_RDONLY);
if (parent < 0)
err_syserr("failed to open FIFO '%s' for reading: ", pipe);
int ret;
if ((ret = read(parent, order_A, sizeof(Order))) > 0)
{
printf("[Child %d]: started work on %d. order.\n", getpid(), order_A->id);
//printf("ret: %d\n", ret);
//printf("%d,%s,%s,%s,%s,%d\n", order_A->id,order_A->fullName,order_A->email,order_A->phone,order_A->created,order_A->performance);
dump_order("Read by child:", order_A);
}
startJob();
char endMessage[256];
sprintf(endMessage, "[Child %d]: ended his daily task.", getpid());
/*Sending the done message via pipe. This is questionable part,
how to do this properly. */
/*child = open(pipe, O_WRONLY);
write(child, &endMessage, strlen(endMessage) + 1);*/
err_remark("Message to parent: %s\n", endMessage);
free(order_A);
exit(0);
}
else
{
//Save the child's pid
shutDown[workerID] = worker;
//Save the child's pipe name.
strcpy(pipes[workerID], pipe);
parent = open(pipe, O_WRONLY);
int ret;
if ((ret = write(parent, &orderA, sizeof(Order))) > 0) // BUG!
{
printf("[Parent]: sending %d. order!\n", orderA->id);
dump_order("Parent sends", orderA);
//printf("ret: %d\n", ret);
}
else
err_syserr("faileds to writ to child %d\n", (int)worker); // Ick!
close(parent);
}
}
void startJob(void)
{
pid_t parentPID = getppid();
sleep(1);
printf("[Child %d]: is done, sending signal.\n", getpid());
if (kill(parentPID, SIGUSR2) != 0)
err_syserr("failed to signal parent process %d\n", (int)parentPID);
else
err_remark("signalled parent process %d with SIGUSR2\n", (int)parentPID);
}
void sendPriorityJobs(node priorityHead)
{
node current = priorityHead;
while (current != NULL)
{
createWorker(current, workerNum);
workerNum++;
current = current->next;
}
err_remark("All priority jobs sent\n");
}
node createNode(void)
{
node tmp;
tmp = (node)malloc(sizeof(struct Order));
tmp->next = NULL;
return tmp;
}
node createOrder(char *fullName, char *email, char *phone, char *created, int performance)
{
node newOrder;
newOrder = createNode();
strcpy(newOrder->fullName, fullName);
strcpy(newOrder->email, email);
strcpy(newOrder->phone, phone);
strcpy(newOrder->created, created);
newOrder->performance = performance;
newOrder->status = 'N';
newOrder->id = orderNum + 1;
orderNum++;
return newOrder;
}
void handler(int signo, siginfo_t *info, void *context)
{
(void)context; // Unused
char msg[256];
time_t t;
time(&t);
sprintf(msg, "[Parent]: i got the signal(%d) from [Child %d] time: %s", signo, info->si_pid, ctime(&t));
err_remark("%s: %d from %d\n", __func__, signo, info->si_pid);
int nbytes = strlen(msg) + 1;
int obytes;
if ((obytes = write(1, msg, nbytes)) != nbytes)
err_syserr("short write %d bytes (%d expected): ", obytes, nbytes);
err_remark("return from %s\n", __func__);
}
Despite its flaws, and the extent to which it ignores the advice on How to avoid using printf()
in a signal handler, it sort of works. Fortunately, the child doesn't try to do anything with the data it is supposed to read from the parent.
Sample run
Parent pid: 89291
signal67: 2018-12-30 15:54:17.298 - pid=89291: FIFO /tmp/child0 created
signal67: 2018-12-30 15:54:17.299 - pid=89292: worker at play!
[Parent]: sending 1. order!
[Child 89292]: started work on 2088763392. order.
signal67: 2018-12-30 15:54:17.299 - pid=89291: Parent sends (0x7fba7c800000):
signal67: 2018-12-30 15:54:17.299 - pid=89291: Order: 1, Jane Doe, test1@gmail.com, 12345678, 2018-12-25 8:00, 1000
signal67: 2018-12-30 15:54:17.299 - pid=89291: FIFO /tmp/child1 created
signal67: 2018-12-30 15:54:17.299 - pid=89292: Read by child: (0x7fba7d001800):
signal67: 2018-12-30 15:54:17.300 - pid=89292: Order: 2088763392, , ?, ?, ?, -413540392
signal67: 2018-12-30 15:54:17.300 - pid=89293: worker at play!
[Parent]: sending 2. order!
signal67: 2018-12-30 15:54:17.300 - pid=89291: Parent sends (0x7fba7c801000):
signal67: 2018-12-30 15:54:17.300 - pid=89291: Order: 2, John Doe, test2@gmail.com, 87654321, 2018-12-25 9:00, 1001
[Child 89293]: started work on 2088767488. order.
signal67: 2018-12-30 15:54:17.300 - pid=89291: All priority jobs sent
signal67: 2018-12-30 15:54:17.300 - pid=89293: Read by child: (0x7fba7d001800):
signal67: 2018-12-30 15:54:17.300 - pid=89293: Order: 2088767488, , ?, ?, ?, -413540392
[Child 89292]: is done, sending signal.
signal67: 2018-12-30 15:54:18.301 - pid=89291: handler: 31 from 89292
[Parent]: i got the signal(31) from [Child 89292] time: Sun Dec 30 15:54:18 2018
signal67: 2018-12-30 15:54:18.301 - pid=89291: return from handler
signal67: 2018-12-30 15:54:18.300 - pid=89292: signalled parent process 89291 with SIGUSR2
[Child 89293]: is done, sending signal.
signal67: 2018-12-30 15:54:18.301 - pid=89291: handler: 31 from 89293
[Parent]: i got the signal(31) from [Child 89293] time: Sun Dec 30 15:54:18 2018
signal67: 2018-12-30 15:54:18.301 - pid=89291: return from handler
signal67: 2018-12-30 15:54:18.301 - pid=89292: Message to parent: [Child 89292]: ended his daily task.
signal67: 2018-12-30 15:54:18.301 - pid=89293: signalled parent process 89291 with SIGUSR2
signal67: 2018-12-30 15:54:18.301 - pid=89293: Message to parent: [Child 89293]: ended his daily task.
signal67: 2018-12-30 15:54:18.302 - pid=89291: child 89292 (corpse 89292) exited with status 0x0000
signal67: 2018-12-30 15:54:18.302 - pid=89291: child 89293 (corpse 89293) exited with status 0x0000
signal67: 2018-12-30 15:54:18.302 - pid=89291: All done!
You can see garbage in the dump of the information received from the parent. However, this does show that the SA_RESTART
flag is important in the sigaction()
call. Without that, the waitpid()
function returns errors, which this program catches and reports.
Version 2
This is mostly working code, which actually uses a sigsuspend()
loop roughly like an earlier incarnation of your code did. However, it is somewhat different. In particular, this gets information back from the children. It does so using the same FIFO that was used to relay the information from the parent to the children. The parent initially opens the FIFO for writing, then writes a message to the child, then closes the FIFO. Meanwhile, the child opens the FIFO for reading, reads the message, and then closes the FIFO. When all the children have signalled the parent (using the sigsuspend
loop), then it reads the responses, with the children opening the FIFO for writing, writing and closing the FIFO, while the parent opens the FIFO for reading, reads the response, and closes the FIFO again. Only then does the parent go into a loop waiting for its children to die (multi-process work on Unix-like systems is a morbid business, what with dead children and zombies and all the rest).
This code also removes the FIFOs both before and after it is run. The FIFO names in the /tmp
directory are easy to deduce, and hence it is easy to break the program (create a directory /tmp/child.0
, for example). A better solution would use mkstemp()
or a similar function to create the FIFO names (noting that mkstemp()
actually creates a file, not a FIFO; there isn't a direct mechanism to create a uniquely-named FIFO that I know of).
Usable code, but still unpolished
/* SO 5396-9266 */
#include "posixver.h"
#include "stderr.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#define MAX_PROCESS 10
static const char defaultPipe[] = "/tmp/child.";
static int orderNum = 0;
static int workerNum = 0;
static pid_t shutDown[MAX_PROCESS];
static char pipes[MAX_PROCESS][100];
struct SigCaught
{
int sig_number;
struct timespec sig_tstamp;
int sig_sender;
void *sig_contxt;
};
static struct SigCaught sig_list[MAX_PROCESS];
static int sig_cur = 0;
static int sig_prt = 0;
typedef struct Order
{
int id;
char created[256];
char fullName[256];
char email[256];
char phone[256];
char status;
int performance;
int days;
struct Order *next;
} Order;
typedef struct Order *node;
static void startJob(void);
static void sendPriorityJobs(node priorityHead);
static void sig_handler(int signo, siginfo_t *info, void *context);
static void sig_printer(void);
static void createWorker(node orderA, int workerID);
static node createNode(void);
static node createOrder(char *fullName, char *email, char *phone, char *created, int performance);
static void get_response(void);
int main(int argc, char **argv)
{
err_setarg0(argv[0]);
if (argc != 1)
err_usage("");
err_setlogopts(ERR_PID|ERR_MILLI);
struct sigaction sa;
sa.sa_handler = (void *)sig_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction(SIGUSR2, &sa, NULL);
node test1 = createOrder("Jane Doe", "test1@gmail.com", "12345678", "2018-12-25 8:00", 1000);
node test2 = createOrder("John Doe", "test2@gmail.com", "87654321", "2018-12-25 9:00", 1001);
test1->next = test2;
printf("Parent pid: %d\n", getpid());
/* This is where we send the task */
sendPriorityJobs(test1);
for (int i = 0; i < workerNum; i++)
{
sigset_t es;
sigemptyset(&es);
err_remark("suspending...\n");
sigsuspend(&es);
err_remark("awake again.\n");
}
sig_printer();
get_response();
/* Wait for child processes to signal, write, and exit */
for (int i = 0; i < workerNum; i++)
{
int status;
int corpse = waitpid(shutDown[i], &status, 0);
if (corpse > 0)
err_remark("child %d (corpse %d) exited with status 0x%.4X\n", shutDown[i], corpse, status);
else
err_sysrem("child %d - no status available: ", shutDown[i]);
}
err_remark("All done!\n");
/* Clean up FIFOs */
for (int i = 0; i < workerNum; i++)
unlink(pipes[i]);
return 0;
}
static void dump_order(const char *tag, const node order)
{
err_remark("%s (%p):\n", tag, (void *)order);
err_remark("Order: %d, %s, %s, %s, %s, %d\n", order->id, order->fullName,
order->email, order->phone, order->created, order->performance);
}
void createWorker(node orderA, int workerID)
{
int parent;
pid_t worker;
char strID[12];
sprintf(strID, "%d", workerID);
char pipe[100];
strcpy(pipe, defaultPipe);
strcat(pipe, strID);
if (unlink(pipe) != 0 && errno != ENOENT)
err_syserr("failed to remove FIFO '%s': ", pipe);
if (mkfifo(pipe, S_IRUSR | S_IWUSR) != 0)
err_syserr("failed to create FIFO '%s': ", pipe);
err_remark("FIFO %s created\n", pipe);
worker = fork();
if (worker < 0)
err_syserr("failed to fork: ");
if (worker == 0)
{
err_remark("worker at play!\n");
node order_A = createNode();
parent = open(pipe, O_RDONLY);
if (parent < 0)
err_syserr("failed to open FIFO '%s' for reading: ", pipe);
int ret;
if ((ret = read(parent, order_A, sizeof(Order))) != sizeof(Order))
err_syserr("short read of %d bytes (%zu expected) from parent: ", ret, sizeof(Order));
printf("[Child %d]: started work on %d. order.\n", getpid(), order_A->id);
dump_order("Read by child:", order_A);
close(parent);
startJob(); /* Signal to parent */
char endMessage[256];
sprintf(endMessage, "[Child %d]: ended his daily task.", getpid());
parent = open(pipe, O_WRONLY);
if (parent < 0)
err_syserr("failed to open FIFO '%s' for writing: ", pipe);
err_remark("successfully reopened FIFO '%s' for writing\n", pipe);
int len = strlen(endMessage);
if (write(parent, endMessage, len) != len)
err_syserr("faied to write message of %d bytes to parent: ", len);
close(parent);
err_remark("Message sent to parent: %s\n", endMessage);
free(order_A);
exit(0);
}
else
{
shutDown[workerID] = worker;
strcpy(pipes[workerID], pipe);
workerID++;
parent = open(pipe, O_WRONLY);
int ret;
if ((ret = write(parent, orderA, sizeof(Order))) == sizeof(Order))
{
printf("[Parent]: sending %d. order to child %d!\n", orderA->id, (int)worker);
dump_order("Parent sends", orderA);
}
else
err_syserr("failed to write %zu bytes to child %d\n", sizeof(Order), (int)worker);
close(parent);
}
}
static void read_response(int worker)
{
err_remark("Starting to read response from worker %d\n", worker);
int fd = open(pipes[worker], O_RDONLY);
if (fd < 0)
err_syserr("failed to open FIFO '%s' for reading\n", pipes[worker]);
err_remark("successfully opened FIFO '%s' for reading\n", pipes[worker]);
int nbytes;
char buffer[1024];
while ((nbytes = read(fd, buffer, sizeof(buffer))) > 0)
err_remark("MSG %i (%d): %.*s\n", worker, shutDown[worker], nbytes, buffer);
fflush(stdout);
close(fd);
err_remark("Finished reading response from worker %d\n", worker);
}
/* There's probably a better way to do this! */
static void get_response(void)
{
for (int i = 0; i < workerNum; i++)
{
for (int j = 0; j < sig_cur; j++)
{
if (shutDown[i] == sig_list[j].sig_sender)
{
read_response(i);
sig_list[j].sig_sender = 0; /* Don't try again */
}
}
}
}
void startJob(void)
{
pid_t parentPID = getppid();
sleep(1);
printf("[Child %d]: is done, sending signal.\n", getpid());
if (kill(parentPID, SIGUSR2) != 0)
err_syserr("failed to signal parent process %d\n", (int)parentPID);
else
err_remark("signalled parent process %d with SIGUSR2\n", (int)parentPID);
}
void sendPriorityJobs(node priorityHead)
{
node current = priorityHead;
while (current != NULL)
{
createWorker(current, workerNum);
workerNum++;
current = current->next;
}
err_remark("All priority jobs sent\n");
}
node createNode(void)
{
node tmp;
tmp = (node)malloc(sizeof(struct Order));
tmp->next = NULL;
return tmp;
}
node createOrder(char *fullName, char *email, char *phone, char *created, int performance)
{
node newOrder;
newOrder = createNode();
strcpy(newOrder->fullName, fullName);
strcpy(newOrder->email, email);
strcpy(newOrder->phone, phone);
strcpy(newOrder->created, created);
newOrder->performance = performance;
newOrder->status = 'N';
newOrder->id = orderNum + 1;
orderNum++;
return newOrder;
}
void sig_handler(int signo, siginfo_t *info, void *context)
{
sig_list[sig_cur].sig_number = signo;
clock_gettime(CLOCK_REALTIME, &sig_list[sig_cur].sig_tstamp);
sig_list[sig_cur].sig_sender = info->si_pid;
sig_list[sig_cur].sig_contxt = context;
static const char sig_message[] = "return from signal handler\n";
write(STDERR_FILENO, sig_message, sizeof(sig_message) - 1);
sig_cur++;
}
static void print_siginfo(struct SigCaught *info)
{
struct tm *lt = localtime(&info->sig_tstamp.tv_sec);
char buffer[32];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", lt);
err_remark("%s.%.3ld: signal %d received from PID %d\n", buffer,
info->sig_tstamp.tv_nsec / 1000000, info->sig_number,
info->sig_sender);
}
static void sig_printer(void)
{
while (sig_prt < sig_cur)
print_siginfo(&sig_list[sig_prt++]);
}
Sample run
Parent pid: 89404
signal41: 2018-12-30 16:02:57.458 - pid=89404: FIFO /tmp/child.0 created
signal41: 2018-12-30 16:02:57.459 - pid=89405: worker at play!
[Child 89405]: started work on 1. order.
[Parent]: sending 1. order to child 89405!
signal41: 2018-12-30 16:02:57.459 - pid=89404: Parent sends (0x7fc14a800000):
signal41: 2018-12-30 16:02:57.459 - pid=89404: Order: 1, Jane Doe, test1@gmail.com, 12345678, 2018-12-25 8:00, 1000
signal41: 2018-12-30 16:02:57.460 - pid=89404: FIFO /tmp/child.1 created
signal41: 2018-12-30 16:02:57.459 - pid=89405: Read by child: (0x7fc14b001800):
signal41: 2018-12-30 16:02:57.460 - pid=89405: Order: 1, Jane Doe, test1@gmail.com, 12345678, 2018-12-25 8:00, 1000
signal41: 2018-12-30 16:02:57.460 - pid=89406: worker at play!
[Child 89406]: started work on 2. order.
[Parent]: sending 2. order to child 89406!
signal41: 2018-12-30 16:02:57.461 - pid=89404: Parent sends (0x7fc14a801000):
signal41: 2018-12-30 16:02:57.461 - pid=89404: Order: 2, John Doe, test2@gmail.com, 87654321, 2018-12-25 9:00, 1001
signal41: 2018-12-30 16:02:57.461 - pid=89404: All priority jobs sent
signal41: 2018-12-30 16:02:57.461 - pid=89404: suspending...
signal41: 2018-12-30 16:02:57.461 - pid=89406: Read by child: (0x7fc14b80a200):
signal41: 2018-12-30 16:02:57.461 - pid=89406: Order: 2, John Doe, test2@gmail.com, 87654321, 2018-12-25 9:00, 1001
[Child 89405]: is done, sending signal.
return from signal handler
signal41: 2018-12-30 16:02:58.461 - pid=89404: awake again.
signal41: 2018-12-30 16:02:58.461 - pid=89404: suspending...
signal41: 2018-12-30 16:02:58.461 - pid=89405: signalled parent process 89404 with SIGUSR2
[Child 89406]: is done, sending signal.
return from signal handler
signal41: 2018-12-30 16:02:58.462 - pid=89404: awake again.
signal41: 2018-12-30 16:02:58.462 - pid=89404: 2018-12-30 16:02:58.461: signal 31 received from PID 89405
signal41: 2018-12-30 16:02:58.462 - pid=89404: 2018-12-30 16:02:58.462: signal 31 received from PID 89406
signal41: 2018-12-30 16:02:58.462 - pid=89404: Starting to read response from worker 0
signal41: 2018-12-30 16:02:58.462 - pid=89404: successfully opened FIFO '/tmp/child.0' for reading
signal41: 2018-12-30 16:02:58.462 - pid=89406: signalled parent process 89404 with SIGUSR2
signal41: 2018-12-30 16:02:58.462 - pid=89405: successfully reopened FIFO '/tmp/child.0' for writing
signal41: 2018-12-30 16:02:58.463 - pid=89404: MSG 0 (89405): [Child 89405]: ended his daily task.
signal41: 2018-12-30 16:02:58.463 - pid=89405: Message sent to parent: [Child 89405]: ended his daily task.
signal41: 2018-12-30 16:02:58.463 - pid=89404: Finished reading response from worker 0
signal41: 2018-12-30 16:02:58.463 - pid=89404: Starting to read response from worker 1
signal41: 2018-12-30 16:02:58.463 - pid=89404: successfully opened FIFO '/tmp/child.1' for reading
signal41: 2018-12-30 16:02:58.463 - pid=89406: successfully reopened FIFO '/tmp/child.1' for writing
signal41: 2018-12-30 16:02:58.463 - pid=89404: MSG 1 (89406): [Child 89406]: ended his daily task.
signal41: 2018-12-30 16:02:58.464 - pid=89404: Finished reading response from worker 1
signal41: 2018-12-30 16:02:58.464 - pid=89404: child 89405 (corpse 89405) exited with status 0x0000
signal41: 2018-12-30 16:02:58.463 - pid=89406: Message sent to parent: [Child 89406]: ended his daily task.
signal41: 2018-12-30 16:02:58.464 - pid=89404: child 89406 (corpse 89406) exited with status 0x0000
signal41: 2018-12-30 16:02:58.464 - pid=89404: All done!