💾 Archived View for thrig.me › blog › 2023 › 03 › 25 › who-goes-first.gmi captured on 2023-05-24 at 18:17:53. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-04-19)
-=-=-=-=-=-=-
Which is executed first? (fork(2) returns twice, should it not fail.)
#include <err.h> #include <stdio.h> #include <unistd.h> int main(void) { pid_t pid = fork(); if (pid < 0) err(1, "fork failed"); if (pid > 0) { fprintf(stderr, "yin\n"); } else { fprintf(stderr, "yang\n"); } }
Usually a sleep(3) call of some sort will be added to ensure that one thing happens after the other. This is a false promise, as the system might become busy beyond that of the sleep interval and then who knows which bit of code happens first. Usually it works out, if you don't mind the waiting and guessing at what a suitable delay should be.
#include <err.h> #include <stdio.h> #include <unistd.h> int main(void) { pid_t pid = fork(); if (pid < 0) err(1, "fork failed"); if (pid > 0) { sleep(5); // KLUGE parent code must happen afterwards fprintf(stderr, "yin\n"); } else { fprintf(stderr, "yang\n"); } }
Sometimes you have to have a delay; I recall a horrid Java thing (in the before sd_notify times) that would take upwards of thirty seconds to disgorge itself from disk, or, sometimes, would fail. Special. So...wait around 45 seconds, then decide if it started or not. In a work environment there is usually not the time to implement things correctly. On the plus side searching for KLUGE is quick and easy and thus I use it to mark things that are probably code smells.
printf("Year: 19%d\n", year); // KLUGE
See how that works?
A different approach is to block one of the processes, and to await a signal. One way to do so is with a pipe.
#include <err.h> #include <stdio.h> #include <unistd.h> enum { READER = 0, WRITER }; int main(void) { int fds[2]; // READER, WRITER if (pipe(fds) == -1) err(1, "pipe failed"); pid_t pid = fork(); if (pid < 0) err(1, "fork failed"); if (pid > 0) { char ch; close(fds[WRITER]); // unused by this process read(fds[READER], &ch, 1); // block until ... close(fds[READER]); fprintf(stderr, "yin\n"); } else { close(fds[READER]); // unused by this process fprintf(stderr, "yang\n"); close(fds[WRITER]); // signal } }
A use for this is if one of the processes must wait for the second to do some necessary steps before the first can begin. Otherwise...who knows who will go first?
The blocked process as written will react to several different signals; two that come to mind are the correct operation of the code, and another case where the other process dies or is killed for some reason. If the unexpected death of the other process is a concern, then a slightly more elaborate communication method will be necessary, perhaps that a particular character is sent. Exactly how to handle this will depend on what is being done and how an unexpected process kill should be handled. My preference is generally to fail the process, not to keep on trying to run something in who knows what state.
#include <err.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> enum { READER = 0, WRITER }; int main(void) { int fds[2]; // READER, WRITER if (pipe(fds) == -1) err(1, "pipe failed"); pid_t pid = fork(); if (pid < 0) err(1, "fork failed"); if (pid > 0) { char ch; close(fds[WRITER]); // unused by this process read(fds[READER], &ch, 1); // block until ... if (ch == 'j') { close(fds[READER]); fprintf(stderr, "yin\n"); } else { errx(1, "no signal??"); } } else { abort(); // whoops! close(fds[READER]); // unused by this process fprintf(stderr, "yang\n"); write(fds[WRITER], "j", 1); // unblock close(fds[WRITER]); } }
A fancy term for a message that can mean different things is the "semipredicate problem".
http://man.openbsd.org/man2/fork.2
http://man.openbsd.org/man2/pipe.2
tags #c #unix