<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://mw.ludd.net/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Adamw</id>
	<title>ludd - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://mw.ludd.net/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Adamw"/>
	<link rel="alternate" type="text/html" href="https://mw.ludd.net/wiki/Special:Contributions/Adamw"/>
	<updated>2026-04-15T03:41:15Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.45.0-alpha</generator>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1844</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1844"/>
		<updated>2025-10-24T11:52:38Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Asynchronous call and communication */ link to doc heading&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage rsync can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM&amp;lt;ref&amp;gt;The virtual machine shared by Erlang, Elixir, Gleam, Ash, and so on: [https://blog.stenmans.org/theBeamBook/ the BEAM Book]&amp;lt;/ref&amp;gt; is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; will often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naïve shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how one would pass it dynamic paths.  It&#039;s unsafe to use string interpolation (&amp;lt;code&amp;gt;&amp;quot;#{source}&amp;quot;&amp;lt;/code&amp;gt; ): consider what could happen if the filenames include unescaped whitespace or special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; passing its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any custom format string following rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/rsyncd.conf.5#log~2 rsyncd.conf log format] docs&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; lines like so:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
       overall percent complete   time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controlling Port captures these lines is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the overall_percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and ignores the leading carriage return before each line, seen in the rsync source code:&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this is the first &amp;quot;control&amp;quot; character we come across and it looks the same as an ordinary byte in the binary data coming over the pipe from rsync, similar to newline &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;.  Its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!  And like newline, carriage return can be ignored.  Control signaling is exactly what goes haywire about this project, and the leaky category distinction between data and control seems to be a repeated theme in inter-process communication.  The reality is not so much data vs. control, as it seems to be a sequence of layers like with [[w:OSI model|networking]].&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character is named after the pushing of a physical typewriter carriage to return to the beginning of the current line without feeding the roller to a new line.&lt;br /&gt;
&lt;br /&gt;
[[File:Baboons Playing in Chobe National Park-crlf.jpg|left|300x300px|Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to find the operating system PID of the child process with &amp;lt;code&amp;gt;Port.info(port, :os_pid)&amp;lt;/code&amp;gt; and send it a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t include built-in functions to send a signal to an OS process, and there is an ugly race condition between closing the port and sending this signal.  We&#039;ll keep looking for another way to &amp;quot;link&amp;quot; the processes.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried spawning  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; instead of rsync and I found that it behaves in exactly the same way: hanging until &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is daemon-like so similar to rsync, but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, for example by calling &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; in a loop:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
size_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (size_read &amp;lt; 0) { error... }&lt;br /&gt;
if (size_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the program does blocking I/O, then a zero-byte &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; indicates the end of file condition.  A program which does asynchronous I/O with &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; might instead detect EOF by listening for the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is can be arranged (TODO: document how this can be done with &amp;lt;code&amp;gt;prctl&amp;lt;/code&amp;gt;, and on which platforms).&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync, here are the business parts:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;// Set up a fail-safe to self-signal with HUP if the controlling process dies.&lt;br /&gt;
prctl(PR_SET_PDEATHSIG, SIGHUP);&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void handle_signal(int signum) {&lt;br /&gt;
  if (signum == SIGHUP &amp;amp;&amp;amp; child_pid &amp;gt; 0) {&lt;br /&gt;
    // Send the child TERM so that rsync can perform clean-up such as shutting down a remote server.&lt;br /&gt;
    kill(child_pid, SIGTERM);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
[[File:Itinerant glassworker exhibition with spinning wheel and steam engine.jpg|thumb]]&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;]&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== TODO: consistency with unix process groups ==&lt;br /&gt;
&lt;br /&gt;
... there is something fun here about how unix already has process tree behaviors which are close analogues to a BEAM supervisor tree.&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1843</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1843"/>
		<updated>2025-10-24T11:50:00Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Reliable clean up */ fix link syntax&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage rsync can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM&amp;lt;ref&amp;gt;The virtual machine shared by Erlang, Elixir, Gleam, Ash, and so on: [https://blog.stenmans.org/theBeamBook/ the BEAM Book]&amp;lt;/ref&amp;gt; is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; will often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naïve shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how one would pass it dynamic paths.  It&#039;s unsafe to use string interpolation (&amp;lt;code&amp;gt;&amp;quot;#{source}&amp;quot;&amp;lt;/code&amp;gt; ): consider what could happen if the filenames include unescaped whitespace or special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; passing its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any custom format string following rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; lines like so:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
       overall percent complete   time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controlling Port captures these lines is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the overall_percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and ignores the leading carriage return before each line, seen in the rsync source code:&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this is the first &amp;quot;control&amp;quot; character we come across and it looks the same as an ordinary byte in the binary data coming over the pipe from rsync, similar to newline &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;.  Its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!  And like newline, carriage return can be ignored.  Control signaling is exactly what goes haywire about this project, and the leaky category distinction between data and control seems to be a repeated theme in inter-process communication.  The reality is not so much data vs. control, as it seems to be a sequence of layers like with [[w:OSI model|networking]].&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character is named after the pushing of a physical typewriter carriage to return to the beginning of the current line without feeding the roller to a new line.&lt;br /&gt;
&lt;br /&gt;
[[File:Baboons Playing in Chobe National Park-crlf.jpg|left|300x300px|Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to find the operating system PID of the child process with &amp;lt;code&amp;gt;Port.info(port, :os_pid)&amp;lt;/code&amp;gt; and send it a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t include built-in functions to send a signal to an OS process, and there is an ugly race condition between closing the port and sending this signal.  We&#039;ll keep looking for another way to &amp;quot;link&amp;quot; the processes.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried spawning  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; instead of rsync and I found that it behaves in exactly the same way: hanging until &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is daemon-like so similar to rsync, but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, for example by calling &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; in a loop:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
size_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (size_read &amp;lt; 0) { error... }&lt;br /&gt;
if (size_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the program does blocking I/O, then a zero-byte &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; indicates the end of file condition.  A program which does asynchronous I/O with &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; might instead detect EOF by listening for the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is can be arranged (TODO: document how this can be done with &amp;lt;code&amp;gt;prctl&amp;lt;/code&amp;gt;, and on which platforms).&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync, here are the business parts:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;// Set up a fail-safe to self-signal with HUP if the controlling process dies.&lt;br /&gt;
prctl(PR_SET_PDEATHSIG, SIGHUP);&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void handle_signal(int signum) {&lt;br /&gt;
  if (signum == SIGHUP &amp;amp;&amp;amp; child_pid &amp;gt; 0) {&lt;br /&gt;
    // Send the child TERM so that rsync can perform clean-up such as shutting down a remote server.&lt;br /&gt;
    kill(child_pid, SIGTERM);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
[[File:Itinerant glassworker exhibition with spinning wheel and steam engine.jpg|thumb]]&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;]&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== TODO: consistency with unix process groups ==&lt;br /&gt;
&lt;br /&gt;
... there is something fun here about how unix already has process tree behaviors which are close analogues to a BEAM supervisor tree.&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1842</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1842"/>
		<updated>2025-10-23T15:22:58Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Future directions */ teaser about process groups&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage rsync can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM&amp;lt;ref&amp;gt;The virtual machine shared by Erlang, Elixir, Gleam, Ash, and so on: [https://blog.stenmans.org/theBeamBook/ the BEAM Book]&amp;lt;/ref&amp;gt; is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; will often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naïve shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how one would pass it dynamic paths.  It&#039;s unsafe to use string interpolation (&amp;lt;code&amp;gt;&amp;quot;#{source}&amp;quot;&amp;lt;/code&amp;gt; ): consider what could happen if the filenames include unescaped whitespace or special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; passing its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any custom format string following rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; lines like so:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
       overall percent complete   time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controlling Port captures these lines is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the overall_percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and ignores the leading carriage return before each line, seen in the rsync source code:&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this is the first &amp;quot;control&amp;quot; character we come across and it looks the same as an ordinary byte in the binary data coming over the pipe from rsync, similar to newline &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;.  Its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!  And like newline, carriage return can be ignored.  Control signaling is exactly what goes haywire about this project, and the leaky category distinction between data and control seems to be a repeated theme in inter-process communication.  The reality is not so much data vs. control, as it seems to be a sequence of layers like with [[w:OSI model|networking]].&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character is named after the pushing of a physical typewriter carriage to return to the beginning of the current line without feeding the roller to a new line.&lt;br /&gt;
&lt;br /&gt;
[[File:Baboons Playing in Chobe National Park-crlf.jpg|left|300x300px|Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to find the operating system PID of the child process with &amp;lt;code&amp;gt;Port.info(port, :os_pid)&amp;lt;/code&amp;gt; and send it a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t include built-in functions to send a signal to an OS process, and there is an ugly race condition between closing the port and sending this signal.  We&#039;ll keep looking for another way to &amp;quot;link&amp;quot; the processes.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried spawning  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; instead of rsync and I found that it behaves in exactly the same way: hanging until &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is daemon-like so similar to rsync, but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, for example by calling &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; in a loop:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
size_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (size_read &amp;lt; 0) { error... }&lt;br /&gt;
if (size_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the program does blocking I/O, then a zero-byte &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; indicates the end of file condition.  A program which does asynchronous I/O with &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; might instead detect EOF by listening for the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is can be arranged (TODO: document how this can be done with &amp;lt;code&amp;gt;prctl&amp;lt;/code&amp;gt;, and on which platforms).&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync, here are the business parts:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;// Set up a fail-safe to self-signal with HUP if the controlling process dies.&lt;br /&gt;
prctl(PR_SET_PDEATHSIG, SIGHUP);&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void handle_signal(int signum) {&lt;br /&gt;
  if (signum == SIGHUP &amp;amp;&amp;amp; child_pid &amp;gt; 0) {&lt;br /&gt;
    // Send the child TERM so that rsync can perform clean-up such as shutting down a remote server.&lt;br /&gt;
    kill(child_pid, SIGTERM);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
[[File:Itinerant glassworker exhibition with spinning wheel and steam engine.jpg|thumb]]&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== TODO: consistency with unix process groups ==&lt;br /&gt;
&lt;br /&gt;
... there is something fun here about how unix already has process tree behaviors which are close analogues to a BEAM supervisor tree.&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1841</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1841"/>
		<updated>2025-10-23T15:21:16Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Bad assumption: pipe-like processes */ correction about when HUP is sent&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage rsync can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM&amp;lt;ref&amp;gt;The virtual machine shared by Erlang, Elixir, Gleam, Ash, and so on: [https://blog.stenmans.org/theBeamBook/ the BEAM Book]&amp;lt;/ref&amp;gt; is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; will often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naïve shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how one would pass it dynamic paths.  It&#039;s unsafe to use string interpolation (&amp;lt;code&amp;gt;&amp;quot;#{source}&amp;quot;&amp;lt;/code&amp;gt; ): consider what could happen if the filenames include unescaped whitespace or special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; passing its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any custom format string following rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; lines like so:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
       overall percent complete   time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controlling Port captures these lines is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the overall_percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and ignores the leading carriage return before each line, seen in the rsync source code:&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this is the first &amp;quot;control&amp;quot; character we come across and it looks the same as an ordinary byte in the binary data coming over the pipe from rsync, similar to newline &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;.  Its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!  And like newline, carriage return can be ignored.  Control signaling is exactly what goes haywire about this project, and the leaky category distinction between data and control seems to be a repeated theme in inter-process communication.  The reality is not so much data vs. control, as it seems to be a sequence of layers like with [[w:OSI model|networking]].&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character is named after the pushing of a physical typewriter carriage to return to the beginning of the current line without feeding the roller to a new line.&lt;br /&gt;
&lt;br /&gt;
[[File:Baboons Playing in Chobe National Park-crlf.jpg|left|300x300px|Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to find the operating system PID of the child process with &amp;lt;code&amp;gt;Port.info(port, :os_pid)&amp;lt;/code&amp;gt; and send it a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t include built-in functions to send a signal to an OS process, and there is an ugly race condition between closing the port and sending this signal.  We&#039;ll keep looking for another way to &amp;quot;link&amp;quot; the processes.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried spawning  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; instead of rsync and I found that it behaves in exactly the same way: hanging until &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is daemon-like so similar to rsync, but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, for example by calling &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; in a loop:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
size_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (size_read &amp;lt; 0) { error... }&lt;br /&gt;
if (size_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the program does blocking I/O, then a zero-byte &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; indicates the end of file condition.  A program which does asynchronous I/O with &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; might instead detect EOF by listening for the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is can be arranged (TODO: document how this can be done with &amp;lt;code&amp;gt;prctl&amp;lt;/code&amp;gt;, and on which platforms).&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync, here are the business parts:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;// Set up a fail-safe to self-signal with HUP if the controlling process dies.&lt;br /&gt;
prctl(PR_SET_PDEATHSIG, SIGHUP);&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void handle_signal(int signum) {&lt;br /&gt;
  if (signum == SIGHUP &amp;amp;&amp;amp; child_pid &amp;gt; 0) {&lt;br /&gt;
    // Send the child TERM so that rsync can perform clean-up such as shutting down a remote server.&lt;br /&gt;
    kill(child_pid, SIGTERM);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
[[File:Itinerant glassworker exhibition with spinning wheel and steam engine.jpg|thumb]]&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1840</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1840"/>
		<updated>2025-10-23T15:20:00Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Problem: runaway processes */ Correct some bad information about port os_pid.  Special thank you to akash-akya&amp;#039;s post https://elixirforum.com/t/any-interest-in-a-library-that-wraps-rsync/69297/10&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage rsync can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM&amp;lt;ref&amp;gt;The virtual machine shared by Erlang, Elixir, Gleam, Ash, and so on: [https://blog.stenmans.org/theBeamBook/ the BEAM Book]&amp;lt;/ref&amp;gt; is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; will often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naïve shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how one would pass it dynamic paths.  It&#039;s unsafe to use string interpolation (&amp;lt;code&amp;gt;&amp;quot;#{source}&amp;quot;&amp;lt;/code&amp;gt; ): consider what could happen if the filenames include unescaped whitespace or special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; passing its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any custom format string following rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; lines like so:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
       overall percent complete   time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controlling Port captures these lines is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the overall_percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and ignores the leading carriage return before each line, seen in the rsync source code:&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this is the first &amp;quot;control&amp;quot; character we come across and it looks the same as an ordinary byte in the binary data coming over the pipe from rsync, similar to newline &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;.  Its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!  And like newline, carriage return can be ignored.  Control signaling is exactly what goes haywire about this project, and the leaky category distinction between data and control seems to be a repeated theme in inter-process communication.  The reality is not so much data vs. control, as it seems to be a sequence of layers like with [[w:OSI model|networking]].&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character is named after the pushing of a physical typewriter carriage to return to the beginning of the current line without feeding the roller to a new line.&lt;br /&gt;
&lt;br /&gt;
[[File:Baboons Playing in Chobe National Park-crlf.jpg|left|300x300px|Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to find the operating system PID of the child process with &amp;lt;code&amp;gt;Port.info(port, :os_pid)&amp;lt;/code&amp;gt; and send it a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t include built-in functions to send a signal to an OS process, and there is an ugly race condition between closing the port and sending this signal.  We&#039;ll keep looking for another way to &amp;quot;link&amp;quot; the processes.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried spawning  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; instead of rsync and I found that it behaves in exactly the same way: hanging until &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is daemon-like so similar to rsync, but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, for example by calling &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; in a loop:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
size_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (size_read &amp;lt; 0) { error... }&lt;br /&gt;
if (size_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the program does blocking I/O, then a zero-byte &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; indicates the end of file condition.  A program which does asynchronous I/O with &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; might instead detect EOF by listening for the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is normally sent when input is closed.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync, here are the business parts:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;// Set up a fail-safe to self-signal with HUP if the controlling process dies.&lt;br /&gt;
prctl(PR_SET_PDEATHSIG, SIGHUP);&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void handle_signal(int signum) {&lt;br /&gt;
  if (signum == SIGHUP &amp;amp;&amp;amp; child_pid &amp;gt; 0) {&lt;br /&gt;
    // Send the child TERM so that rsync can perform clean-up such as shutting down a remote server.&lt;br /&gt;
    kill(child_pid, SIGTERM);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
[[File:Itinerant glassworker exhibition with spinning wheel and steam engine.jpg|thumb]]&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1839</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1839"/>
		<updated>2025-10-21T15:49:33Z</updated>

		<summary type="html">&lt;p&gt;Adamw: light edits&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage rsync can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM&amp;lt;ref&amp;gt;The virtual machine shared by Erlang, Elixir, Gleam, Ash, and so on: [https://blog.stenmans.org/theBeamBook/ the BEAM Book]&amp;lt;/ref&amp;gt; is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; will often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naïve shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how one would pass it dynamic paths.  It&#039;s unsafe to use string interpolation (&amp;lt;code&amp;gt;&amp;quot;#{source}&amp;quot;&amp;lt;/code&amp;gt; ): consider what could happen if the filenames include unescaped whitespace or special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; passing its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any custom format string following rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; lines like so:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
       overall percent complete   time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controlling Port captures these lines is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the overall_percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and ignores the leading carriage return before each line, seen in the rsync source code:&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this is the first &amp;quot;control&amp;quot; character we come across and it looks the same as an ordinary byte in the binary data coming over the pipe from rsync, similar to newline &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;.  Its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!  And like newline, carriage return can be ignored.  Control signaling is exactly what goes haywire about this project, and the leaky category distinction between data and control seems to be a repeated theme in inter-process communication.  The reality is not so much data vs. control, as it seems to be a sequence of layers like with [[w:OSI model|networking]].&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character is named after the pushing of a physical typewriter carriage to return to the beginning of the current line without feeding the roller to a new line.&lt;br /&gt;
&lt;br /&gt;
[[File:Baboons Playing in Chobe National Park-crlf.jpg|left|300x300px|Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to send a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t expose the child process ID and doesn&#039;t include any built-in functions to send a signal to an OS process.  Clearly we&#039;re expected to do this another way.  Another problem with &amp;quot;kill&amp;quot; is that we want the external process to stop no matter how badly the BEAM is damaged, so we shouldn&#039;t rely on stored data or on running final clean-up logic before exiting.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried spawning  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; instead of rsync and I found that it behaves in exactly the same way: hanging until &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is daemon-like so similar to rsync, but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, for example by calling &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; in a loop:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
size_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (size_read &amp;lt; 0) { error... }&lt;br /&gt;
if (size_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the program does blocking I/O, then a zero-byte &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; indicates the end of file condition.  A program which does asynchronous I/O with &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; might instead detect EOF by listening for the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is normally sent when input is closed.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync, here are the business parts:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;// Set up a fail-safe to self-signal with HUP if the controlling process dies.&lt;br /&gt;
prctl(PR_SET_PDEATHSIG, SIGHUP);&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void handle_signal(int signum) {&lt;br /&gt;
  if (signum == SIGHUP &amp;amp;&amp;amp; child_pid &amp;gt; 0) {&lt;br /&gt;
    // Send the child TERM so that rsync can perform clean-up such as shutting down a remote server.&lt;br /&gt;
    kill(child_pid, SIGTERM);&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
[[File:Itinerant glassworker exhibition with spinning wheel and steam engine.jpg|thumb]]&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1838</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1838"/>
		<updated>2025-10-21T13:51:49Z</updated>

		<summary type="html">&lt;p&gt;Adamw: memed.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage rsync can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM&amp;lt;ref&amp;gt;The virtual machine shared by Erlang, Elixir, Gleam, Ash, and so on: [https://blog.stenmans.org/theBeamBook/ the BEAM Book]&amp;lt;/ref&amp;gt; is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; will often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naïve shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how one would pass it dynamic paths.  It&#039;s unsafe to use string interpolation (&amp;lt;code&amp;gt;&amp;quot;#{source}&amp;quot;&amp;lt;/code&amp;gt; ): consider what could happen if the filenames include unescaped whitespace or special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; passing its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any custom format string following rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; lines like so:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
       overall percent complete   time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The controlling Port captures these lines is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the overall_percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and ignores the leading carriage return before each line, seen in the rsync source code:&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this is the first &amp;quot;control&amp;quot; character we come across and it looks the same as an ordinary byte in the binary data coming over the pipe from rsync, similar to newline &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;.  Its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!  And like newline, carriage return can be ignored.  Control signaling is exactly what goes haywire about this project, and the leaky category distinction between data and control seems to be a repeated theme in inter-process communication.  The reality is not so much data vs. control, as it seems to be a sequence of layers like with [[w:OSI model|networking]].&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character is named after the pushing of a physical typewriter carriage to return to the beginning of the current line without feeding the roller to a new line.&lt;br /&gt;
&lt;br /&gt;
[[File:Baboons Playing in Chobe National Park-crlf.jpg|left|300x300px|Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to send a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t expose the child process ID and doesn&#039;t include any built-in functions to send a signal to an OS process.  Clearly we&#039;re expected to do this another way.  Another problem with &amp;quot;kill&amp;quot; is that we want the external process to stop no matter how badly the BEAM is damaged, so we shouldn&#039;t rely on stored data or on running final clean-up logic before exiting.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried to spawn  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; using the same Port command, and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is unusual in the same way as rsync but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, by making regular C system calls to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;When the program uses blocking I/O, reading zero bytes indicates the end of file.  There are also programs which do asynchronous I/O using &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt;, and these might rely on the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is normally sent when input is closed.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
[[File:Itinerant glassworker exhibition with spinning wheel and steam engine.jpg|thumb]]&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=File:Baboons_Playing_in_Chobe_National_Park-crlf.jpg&amp;diff=1837</id>
		<title>File:Baboons Playing in Chobe National Park-crlf.jpg</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=File:Baboons_Playing_in_Chobe_National_Park-crlf.jpg&amp;diff=1837"/>
		<updated>2025-10-21T13:51:06Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Uploaded a work by  Faypearse from https://commons.wikimedia.org/wiki/File:Baboons_Playing_in_Chobe_National_Park.jpg with UploadWizard&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=={{int:filedesc}}==&lt;br /&gt;
{{Information&lt;br /&gt;
|description={{en|1=Three young baboons playing on a rock ledge.  Two are on the ridge and one below, grabbing the tail of another.  A meme font shows &amp;quot;\r&amp;quot;, &amp;quot;\n&amp;quot;, and &amp;quot;\r\n&amp;quot; personified as each baboon.}}&lt;br /&gt;
|date=2025-10-21&lt;br /&gt;
|source=https://commons.wikimedia.org/wiki/File:Baboons_Playing_in_Chobe_National_Park.jpg&lt;br /&gt;
|author= Faypearse&lt;br /&gt;
|permission=&lt;br /&gt;
|other versions=&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=={{int:license-header}}==&lt;br /&gt;
{{cc-by-sa-4.0}}&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1836</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1836"/>
		<updated>2025-10-20T14:41:31Z</updated>

		<summary type="html">&lt;p&gt;Adamw: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage it can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; would often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naive shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how we pass the filenames.  It would be possible to pass a dynamic path using string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; but this is risky: consider what happens if the filenames include whitespace or even special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; which passes its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
We&#039;ve chosen &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; , so the meaning of the reported percentage is &amp;quot;overall percent complete&amp;quot;.  Rsync outputs these progress lines in a fairly self-explanatory columnar format:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
          percent complete       time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Our Port captures output and each line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—even skipping the leading carriage return that can be seen in the rsync source code,&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!&lt;br /&gt;
&lt;br /&gt;
A repeated theme in inter-process communication is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
# echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
twoee&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.&lt;br /&gt;
&lt;br /&gt;
The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to send a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t expose the child process ID and doesn&#039;t include any built-in functions to send a signal to an OS process.  Clearly we&#039;re expected to do this another way.  Another problem with &amp;quot;kill&amp;quot; is that we want the external process to stop no matter how badly the BEAM is damaged, so we shouldn&#039;t rely on stored data or on running final clean-up logic before exiting.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried to spawn  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; using the same Port command, and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is unusual in the same way as rsync but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  We can roughly group the different styles of command-line application into &amp;quot;pipeline&amp;quot; programs which read and write, &amp;quot;interactive&amp;quot; programs which require user input, and &amp;quot;daemon&amp;quot; programs which are designed to run in the background.  Some programs support multiple modes depending on the arguments given at launch, or by detecting the terminal using &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/isatty.3.en docs for &amp;lt;code&amp;gt;isatty&amp;lt;/code&amp;gt;]&amp;lt;/ref&amp;gt;.  The BEAM is currently optimized to interface with pipeline programs and it assumes that the external process will stop when its &amp;quot;standard input&amp;quot; is closed.&lt;br /&gt;
&lt;br /&gt;
A typical pipeline program will stop once it detects that input has ended, by making regular C system calls to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;When the program uses blocking I/O, reading zero bytes indicates the end of file.  There are also programs which do asynchronous I/O using &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt;, and these might rely on the &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; hang-up signal which is normally sent when input is closed.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes can more generally affect each other through pipes.  Surprising answer: without much effect!  You can experiment with the &amp;lt;code&amp;gt;/dev/null&amp;lt;/code&amp;gt; device which behaves like a closed pipe, for example compare these two commands:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
cat &amp;lt; /dev/null&lt;br /&gt;
&lt;br /&gt;
sleep 10 &amp;lt; /dev/null&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; exits immediately, but &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; does its thing as usual.&lt;br /&gt;
&lt;br /&gt;
You could do the same experiment by opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Interestingly, what happened here is that &amp;lt;control&amp;gt;-d is interpreted by bash which responds by closing its pipe connected to standard input of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Dump information about your own terminal emulator: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  A program with a chaotic disposition could even reopen stdin after it was closed and connect it to something else, to the great surprise of friends and neighbors.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in the category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
A small shim can adapt a daemon-like program to behave more like a pipeline.  The shim is sensitive to stdin closing or SIGHUP, and when this is detected it converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir, and the &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter can be found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits, so that a pipe-like program can behave more like a daemon.&lt;br /&gt;
&lt;br /&gt;
I used the shim approach in my rsync library and it includes a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to BEAM &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt;.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they deserve their reputation for being friendly and open.  The first big tip was to look at the third-party library &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt;&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;, which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem generally agrees that the fragile clean up of external processes is a bug, and supports the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs when the port is closed.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in an auxiliary C program and not written in Erlang or even in the BEAM itself, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en Overview of unix pipes]&amp;lt;/ref&amp;gt;, using libc&#039;s pipe&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.2.en Docs for the &amp;lt;code&amp;gt;pipe&amp;lt;/code&amp;gt; syscall]&amp;lt;/ref&amp;gt;, read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS, and several levels of port wiring are involved in launching external processes: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.  This architecture reflects the Supervisor paradigm and we can leverage it to produce some of the same properties: the subprocess can buffer reads and writes asynchronously and handle them sequentially; and if the BEAM crashes then erl_child_setup can detect the condition and do its own cleanup.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive its controlling process leaves the child in a state called &amp;quot;orphaned&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : sent to a process when its standard input stream is closed&amp;lt;ref&amp;gt;[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard &amp;quot;General Terminal Interface: Modem Disconnect&amp;quot;&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1835</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1835"/>
		<updated>2025-10-20T12:26:14Z</updated>

		<summary type="html">&lt;p&gt;Adamw: c/e&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage it can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: an external dependency like &amp;quot;cron&amp;quot; would often be ported into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naive shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how we pass the filenames.  It would be possible to pass a dynamic path using string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; but this is risky: consider what happens if the filenames include whitespace or even special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; which passes its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
We&#039;ve chosen &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; , so the meaning of the reported percentage is &amp;quot;overall percent complete&amp;quot;.  Rsync outputs these progress lines in a fairly self-explanatory columnar format:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
          percent complete       time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Our Port captures output and each line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the percent_done column and flag any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when percent_done_text != nil &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—even skipping the leading carriage return that can be seen in the rsync source code,&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but its normal role is to control the terminal emulator, rewinding the cursor so that the current line can be overwritten!&lt;br /&gt;
&lt;br /&gt;
A repeated theme in inter-process communication is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.  Spoiler: the output should read &amp;quot;twoee&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but Erlang/OTP really starts to shine once we wrap each Port connection under a &amp;lt;code&amp;gt;gen_server&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us several properties for free: A dedicated application thread coordinates with its rsync process independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  The gen_server holds internal state including the up-to-date completion percentage.  And the caller can request updates as needed, or it can listen for push messages with the parsed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server is also expected to run safely under an OTP supervision tree&amp;lt;ref&amp;gt;[https://adoptingerlang.org/docs/development/supervision_trees/ &amp;quot;Supervision Trees&amp;quot;] chapter from [https://adoptingerlang.org/ Adopting Erlang]&amp;lt;/ref&amp;gt; but this is where our dream falls apart for the moment.  The Port already watches for rsync completion or failure and reports upwards to its caller, but we fail at the critical property of being able to propagate a termination downwards to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence is that rsync transfers will continue to run in the background even after Elixir kills our gen_server or shuts down, because the BEAM has no way of stopping the external process.&lt;br /&gt;
&lt;br /&gt;
It&#039;s possible to send a signal by shelling out to unix &amp;lt;code&amp;gt;kill PID&amp;lt;/code&amp;gt;, but BEAM doesn&#039;t expose the child process ID and doesn&#039;t include any built-in functions to send a signal to an OS process.  Clearly we&#039;re expected to do this another way.  Another problem with &amp;quot;kill&amp;quot; is that we want the external process to stop no matter how badly the BEAM is damaged, so we shouldn&#039;t rely on stored data or on running final clean-up logic before exiting.&lt;br /&gt;
&lt;br /&gt;
To debug what happens during &amp;lt;code&amp;gt;port_close&amp;lt;/code&amp;gt; and to eliminate variables, I tried to spawn  &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; using the same Port command, and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice as I learned later: &amp;quot;sleep&amp;quot; is unusual in the same way as rsync but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  These will stop once they detects that input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en overview of unix pipes]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1834</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1834"/>
		<updated>2025-10-20T10:08:04Z</updated>

		<summary type="html">&lt;p&gt;Adamw: light c/e&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&amp;lt;p&amp;gt;[[w:rsync|Rsync]] is the standard utility for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage it can be trusted to handle any edge case.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;BEAM is a fairly unique ecosystem in which it&#039;s not considered deviant to reinvent a rounder wheel: it&#039;s common to port external dependencies into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.&amp;lt;/p&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naive shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how we pass the filenames.  It would be possible to pass a dynamic path using string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; but this is risky: consider what happens if the filenames include whitespace or even special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
We turn next to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this is perfect, but for longer transfers our program loses control and observability, waiting indefinitely for a monolithic command to return.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt;, nothing but a one-line wrapper&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; which passes its parameters directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2 Erlang &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.  This function is tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
&#039;&#039;&#039;Rsync progress reporting options&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There are a variety of ways to report progress:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;-v&amp;lt;/code&amp;gt; : list each filename as it&#039;s transferred&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
We&#039;ve chosen &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; , so the meaning of the reported percentage is &amp;quot;overall percent complete&amp;quot;.  Rsync outputs these progress lines in a fairly self-explanatory columnar format:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
          percent complete       time remaining&lt;br /&gt;
bytes transferred |  transfer speed    |&lt;br /&gt;
         |        |        |           |&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Our Port captures output and each line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the percent_done column and log any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and even a leading carriage return that we can see in the rsync source code,&amp;lt;ref&amp;gt;[https://github.com/RsyncProject/rsync/blob/797e17fc4a6f15e3b1756538a9f812b63942686f/progress.c#L129 rsync/progress.c] source code&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but its normal role is playing a control function because of how the terminal emulator responds to it.  On a terminal the effect is to rewind the cursor so that the current line can be overwritten!&lt;br /&gt;
&lt;br /&gt;
A repeated theme in inter-process communication is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
echo &amp;quot;three^Mtwo&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return, copy-and-paste won&#039;t work.  Spoiler: the output should read &amp;quot;twoee&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but where Erlang/OTP really starts to shine is when we wrap each Port connection under a gen_server&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/apps/stdlib/gen_server.html Erlang gen_server docs]&amp;lt;/ref&amp;gt; module, giving us some properties for free: A dedicated thread coordinates with its rsync independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  It holds internal state including the up-to-date completion percentage.  And the caller can either request updates manually, or it can listen for pushed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server should also be able to run under an [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] but this is where the dream falls apart, for the moment.  The Port can watch for rsync completion or failure and report this to its caller, but we fail at the second critical property of being able to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence of this limitation is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
It might be possible to send a signal using unix &amp;quot;kill&amp;quot;, but BEAM doesn&#039;t expose the child process ID and it doesn&#039;t include any built-in commands to send a signal.  Clearly we&#039;re expected to do this another way.  Another problem with &amp;quot;kill&amp;quot; is that we want the external process to stop no matter how badly the BEAM is damaged so we can&#039;t rely on stored data and on making a few last calls before crashing.&lt;br /&gt;
&lt;br /&gt;
To eliminate variable and to understand whether the failure to stop was specific to rsync, I tried the same Port command but spawning a &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt;, and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice, as I learned later that &amp;quot;sleep&amp;quot; is also unusual but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  These will stop once they detects that input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/read.2 libc &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/pipe.7.en overview of unix pipes]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;[https://wizardzines.com/comics/stty/ ★ wizard zines ★: stty]&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;[https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes Elixir Port docs showing a shim script]&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html &amp;lt;code&amp;gt;erlexec&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;[https://github.com/shortishly/grimsby &amp;lt;code&amp;gt;grimsby&amp;lt;/code&amp;gt; library]&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;[https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads rsync_ex C shim program]&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;[https://man.archlinux.org/man/select.2.en libc &amp;lt;code&amp;gt;select&amp;lt;/code&amp;gt; docs]&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;[https://www.erlang.org/doc/system/ports.html Erlang ports docs]&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1833</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1833"/>
		<updated>2025-10-19T11:11:04Z</updated>

		<summary type="html">&lt;p&gt;Adamw: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress, using rsync.&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=[[w:rsync|Rsync]] is the best tool for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage it can be trusted to handle all the edge cases.&lt;br /&gt;
&lt;br /&gt;
BEAM is a fairly unique ecosystem in which the philosophy is to constantly reinvent a rounder wheel: it&#039;s common to port external dependencies into native Erlang—but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.}}&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naive shelling ===&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how we pass the filenames.  It&#039;s possible to have a dynamic path coming from string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; but this gets risky: consider what happens if the filenames include whitespace or even special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
Skipping ahead to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this would be fine, but during longer transfers our program loses control and we have to wait indefinitely for the monolithic command to finish.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which passes all of its parameters directly&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs progress lines in a fairly self-explanatory format:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Our Port captures output and each line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the percent_done column and log any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and even a leading carriage return that we can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but its normal role is playing a control function because of how the terminal emulator responds to it.  On a terminal the effect is to rewind the cursor and overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme in inter-process communication is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by beginning each line with a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  Try this command in a terminal:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
echo &amp;quot;one^Mtwo&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You&#039;ll have to use &amp;lt;control&amp;gt;-v &amp;lt;control&amp;gt;-m to type a literal carriage return.  Spoiler: the output should read &amp;quot;two&amp;quot; and nothing else.&lt;br /&gt;
&lt;br /&gt;
The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
The Port API is convenient enough so far, but where Erlang/OTP really starts to shine is when we wrap each Port connection under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module, giving us some properties for free: A dedicated thread coordinates with its rsync independent of anything else.  Input and output are asynchronous and buffered, but handled sequentially in a thread-safe way.  It holds internal state including the up-to-date completion percentage.  And the caller can either request updates manually, or it can listen for pushed statistics.&lt;br /&gt;
&lt;br /&gt;
This gen_server should also be able to run under an [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] but this is where the dream falls apart, for the moment.  The Port can watch for rsync completion or failure and report this to its caller, but we fail at the second critical property of being able to shut down rsync if the calling code or our library module crashes.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
The unpleasant real-world consequence of this limitation is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
It might be possible to send a signal using unix &amp;quot;kill&amp;quot;, but BEAM doesn&#039;t expose the child process ID and it doesn&#039;t include any built-in commands to send a signal.  Clearly we&#039;re expected to do this another way.  Another problem with &amp;quot;kill&amp;quot; is that we want the external process to stop no matter how badly the BEAM is damaged so we can&#039;t rely on stored data and on making a few last calls before crashing.&lt;br /&gt;
&lt;br /&gt;
To eliminate variable and to understand whether the failure to stop was specific to rsync, I tried the same Port command but spawning a &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt;, and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.  This happens to have been a lucky choice, as I learned later that &amp;quot;sleep&amp;quot; is also unusual but its behavior is much simpler to reason about.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A pipeline like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; it built to read from its input and write to its output.  These will stop once they detects that input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1832</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1832"/>
		<updated>2025-10-19T10:52:42Z</updated>

		<summary type="html">&lt;p&gt;Adamw: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress, using rsync.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=[[w:rsync|Rsync]] is the best tool for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage it can be trusted to handle all the edge cases.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
BEAM is a fairly unique ecosystem in which the philosophy is to constantly reinvent a rounder wheel: it&#039;s common to port external dependencies into native Erlang, but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.}}&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naive shelling ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how we pass the filenames.  It&#039;s possible to have a dynamic path coming from string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; but this gets risky: consider what happens if the filenames include whitespace or even special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
Skipping ahead to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this would be fine, but during longer transfers our program loses control and we have to wait indefinitely for the monolithic command to finish.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which passes all of its parameters directly&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Rsync outputs progress lines in a fairly self-explanatory format:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Our Port captures output and each line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;.  After the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt; message.&lt;br /&gt;
&lt;br /&gt;
As a first step, we extract the percent_done column and log any unrecognized output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; is lifting more than its weight here: it lets us completely ignore spacing and newline trickery—and even a leading carriage return that we can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but its normal role is playing a control function because of how the terminal emulator responds to it.  On a terminal the effect is to rewind the cursor and overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme in inter-process communication is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. line feed has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1831</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1831"/>
		<updated>2025-10-19T10:44:34Z</updated>

		<summary type="html">&lt;p&gt;Adamw: more aside&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress, using rsync.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=[[w:rsync|Rsync]] is the best tool for file transfers, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and after almost 30 years of usage it can be trusted to handle all the edge cases.&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
BEAM is a fairly unique ecosystem in which the philosophy is to constantly reinvent a rounder wheel: it&#039;s common to port external dependencies into native Erlang, but the complexity of rsync and its dependence on a matching remote daemon makes it unlikely that it will be rewritten any time soon, which is why I&#039;ve decided to wrap external command execution in a library.}}&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naive shelling ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how we pass the filenames.  It&#039;s possible to have a dynamic path coming from string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; but this gets risky: consider what happens if the filenames include whitespace or even special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
Skipping ahead to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this would be fine, but during longer transfers our program loses control and we have to wait indefinitely for the monolithic command to finish.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which passes all of its parameters directly&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1830</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1830"/>
		<updated>2025-10-19T10:39:13Z</updated>

		<summary type="html">&lt;p&gt;Adamw: c/e first page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality library for Elixir to transfer files in the background and monitor progress, using rsync.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=[[w:rsync|Rsync]] is usually the best tool for file transfer, locally or over a network.  It can resume incomplete transfers and synchronize directories efficiently, and it&#039;s complex enough that nobody is reimplementing it in pure Erlang any time soon.}}&lt;br /&gt;
&lt;br /&gt;
I was excited to learn how to interface with long-lived external processes—and this project offered more than I hoped for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
=== Naive shelling ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with how we pass the filenames.  It&#039;s possible to have a dynamic path coming from string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; but this gets risky: consider what happens if the filenames include whitespace or even special shell characters such as &amp;quot;;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Safe path handling ===&lt;br /&gt;
Skipping ahead to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;, which takes a raw argv and can&#039;t be fooled special characters in the path arguments:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;For a short job this would be fine, but during longer transfers our program loses control and we have to wait indefinitely for the monolithic command to finish.&lt;br /&gt;
&lt;br /&gt;
=== Asynchronous call and communication ===&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which passes all of its parameters directly&amp;lt;ref&amp;gt;See the [https://github.com/elixir-lang/elixir/blob/809b035dccf046b7b7b4422f42cfb6d075df71d2/lib/elixir/lib/port.ex#L232 port.ex source code]&amp;lt;/ref&amp;gt; to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, here we turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1829</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1829"/>
		<updated>2025-10-17T12:12:42Z</updated>

		<summary type="html">&lt;p&gt;Adamw: future directions section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Future directions ==&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.&lt;br /&gt;
&lt;br /&gt;
There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
Another idea to borrow from the erlexec library is to have an option to kill the entire process group of a child, which is shared by any descendants that haven&#039;t explicitly broken out of its original group.  This would be useful for managing deep trees of external processes launched by a forked command.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Draft:Elixir/Ports_and_external_process_wiring&amp;diff=1828</id>
		<title>Draft:Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Draft:Elixir/Ports_and_external_process_wiring&amp;diff=1828"/>
		<updated>2025-10-17T11:57:57Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Adamw moved page Draft:Elixir/Ports and external process wiring to Elixir/Ports and external process wiring&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Elixir/Ports and external process wiring]]&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1827</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1827"/>
		<updated>2025-10-17T11:57:57Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Adamw moved page Draft:Elixir/Ports and external process wiring to Elixir/Ports and external process wiring&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1826</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1826"/>
		<updated>2025-10-17T11:57:43Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Reliable clean up */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates emerging best practices which could be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly in a C program and not actually in Erlang, but it was still fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely, for example.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1825</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1825"/>
		<updated>2025-10-17T11:56:06Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Shimming can kill */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in and/or standard out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took the shim approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (but should have been SIGTERM, see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates best practices that can be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly to a C program and not actually in Erlang, but it was fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1824</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1824"/>
		<updated>2025-10-17T11:54:44Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Bad assumption: pipe-like processes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: it doesn&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in or out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took this approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates best practices that can be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly to a C program and not actually in Erlang, but it was fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1823</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1823"/>
		<updated>2025-10-17T11:53:13Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* Problem: runaway processes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM has no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried to open a Port spawning the command &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: they don&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in or out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took this approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates best practices that can be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly to a C program and not actually in Erlang, but it was fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1822</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1822"/>
		<updated>2025-10-17T11:52:26Z</updated>

		<summary type="html">&lt;p&gt;Adamw: /* OTP generic server */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: although it can correctly detect and report when rsync crashes or completes, when our gen_server is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM had no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried the same thing with &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: they don&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in or out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took this approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates best practices that can be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly to a C program and not actually in Erlang, but it was fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1821</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1821"/>
		<updated>2025-10-17T11:50:21Z</updated>

		<summary type="html">&lt;p&gt;Adamw: light edits&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background while monitoring progress.  Rsync is the best tool for this since it can resume incomplete transfers and synchronize directories efficiently and it&#039;s complex enough that nobody will reimplement it in pure Erlang.  I had hoped that this project would teach me how to interface with long-lived external processes—and I learned more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused to make this dynamic so let&#039;s skip ahead to how to &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which is safer because it doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Better but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we will reach for Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These functions are tremendously flexible, and here we demonstrate how to turn a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--out-format=FORMAT&amp;lt;/code&amp;gt; : any format using parameters from rsyncd.conf&#039;s &amp;lt;code&amp;gt;log format&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.freebsd.org/cgi/man.cgi?query=rsyncd.conf&amp;lt;/ref&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should also be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: it can correctly detect and report when rsync crashes or completes, but if our module is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM had no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried the same thing with &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: they don&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in or out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took this approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates best practices that can be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly to a C program and not actually in Erlang, but it was fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1820</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1820"/>
		<updated>2025-10-17T11:38:29Z</updated>

		<summary type="html">&lt;p&gt;Adamw: c/e to the end&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background and can monitor progress.  I hoped to learn better how to interface with long-lived external processes—and I got more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|300x300px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused so let&#039;s skip straight to the next tool,  &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is safer, but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s lowest-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These are tremendously flexible, here we demonstrate turning a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The carriage return &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; deserves a special mention: this &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  On the terminal the effect is to overwrite the current line!&lt;br /&gt;
&lt;br /&gt;
A repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later.&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
[[File:Chinese typewriter 03.jpg|right|200x200px]]&lt;br /&gt;
&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character, &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; sometimes rendered as &amp;lt;code&amp;gt;^M&amp;lt;/code&amp;gt;.  The character seems to be named after pushing the physical paper carriage of a typewriter back to the beginning of the line without feeding the roller.&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreement about carriage return]] vs. newline has caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
&lt;br /&gt;
[[File:Nilgais fighting, Lakeshwari, Gwalior district, India.jpg|left|200x200px]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== OTP generic server ==&lt;br /&gt;
This is where Erlang/OTP really starts to shine: our rsync library wraps the Port calls under a gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; module and this gives us some special properties for free: a dedicated thread which coordinates with rsync independently from anything else, receiving and sending asynchronous messages.  It has an internal state including the latest percent done and this can be probed by calling code, or it can be set up to push updates to a listener.&lt;br /&gt;
&lt;br /&gt;
A gen_server should also be able to run under a [https://adoptingerlang.org/docs/development/supervision_trees/ OTP supervision tree] as well but our module has a major flaw: it can correctly detect and report when rsync crashes or completes, but if our module is stopped by its supervisor it cannot stop its external child process in turn.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
[[File:CargoNet Di 12 Euro 4000 Lønsdal - Bolna.jpg|thumb]]&lt;br /&gt;
What this means is that rsync transfers would continue to run in the background even after Elixir had completely shut down, because the BEAM had no way of stopping the process.&lt;br /&gt;
&lt;br /&gt;
To check whether this was something specific to rsync, I tried the same thing with &amp;lt;code&amp;gt;sleep 60&amp;lt;/code&amp;gt; and I found that it behaves exactly the same way, hanging until the sleep ends naturally regardless of what happened in Elixir or whether its pipes are still open.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: they don&#039;t affect very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, though—the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing its pipe connected to &amp;quot;[[w:Standard streams|standard input]]&amp;quot; of the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead and try this command, what could go wrong: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a hard reality.  You could even reopen stdin from the application, to the great surprise of your friends and neighbors.  For example, try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cared, it wasn&#039;t listening to you anyway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, &amp;quot;rsync&amp;quot; is in this latter category of &amp;quot;daemon-like&amp;quot; programs which will carry on even after standard input is closed.  This makes sense enough, since rsync isn&#039;t interactive and any output is just a side effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
== Shimming can kill ==&lt;br /&gt;
It&#039;s possible to write a small adapter which is sensitive to stdin closing, then converts this into a stronger signal like SIGTERM which it forwards to its own child.  This is the idea behind a suggested shell script&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/1.19.0/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt; for Elixir and the erlexec&amp;lt;ref&amp;gt;[https://hexdocs.pm/erlexec/readme.html https://hexdocs.pm/erlexec/]&amp;lt;/ref&amp;gt; library.  The opposite adapter is also found in the [[w:nohup|nohup]] shell command and the grimsby&amp;lt;ref&amp;gt;https://github.com/shortishly/grimsby&amp;lt;/ref&amp;gt; library: these will keep standard in or out open for the child process even after the parent exits.&lt;br /&gt;
&lt;br /&gt;
I took this approach with my rsync library and included a small C program&amp;lt;ref&amp;gt;https://gitlab.com/adamwight/rsync_ex/-/blob/main/src/main.c?ref_type=heads&amp;lt;/ref&amp;gt; which wraps rsync and makes it sensitive to the BEAM port_close.  It&#039;s featherweight, leaving pipes unchanged as it passes control to rsync—its only real effect is to convert SIGHUP to SIGKILL (see the sidebar discussion of different signals below).&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
{{Project|status=in review|url=https://erlangforums.com/t/open-port-and-zombie-processes|source=https://github.com/erlang/otp/pull/9453}}&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned their reputation for being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates best practices that can be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that some flavor of &amp;quot;terminate&amp;quot; signal should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
I would be lying to hide my disappointment that the required core changes are mostly to a C program and not actually in Erlang, but it was fascinating to open such an elegant black box and find the technological equivalent of a steam engine inside.  All of the futuristic, high-level features we&#039;ve come to know actually map closely to a few scraps of wizardry with ordinary pipes, using stdlib read, write, and select&amp;lt;ref&amp;gt;https://man.archlinux.org/man/select.2.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Port drivers&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/ports.html&amp;lt;/ref&amp;gt; are fundamental to ERTS and external processes are launched through several levels of wiring: the spawn driver starts a forker driver which sends a control message to &amp;lt;code&amp;gt;erl_child_setup&amp;lt;/code&amp;gt; to execute your external command.  Each BEAM has a single erl_child_setup process to watch over all children.&lt;br /&gt;
&lt;br /&gt;
Letting a child process outlive the one that spawned leaves it in a state called an &amp;quot;orphaned process&amp;quot; in POSIX, and the standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists.  This can be seen as undesirable because unix itself has a paradigm similar to OTP&#039;s Supervisors, in which each parent is responsible for its children.  Without supervision, a process could potentially run forever or do naughty things.  The system &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; process starts and tracks its own children, and can restart them in response to service commands.  But init will know nothing about adopted, orphan processes or how to monitor and restart them.&lt;br /&gt;
&lt;br /&gt;
The patch [https://github.com/erlang/otp/pull/9453 PR#9453] adapting port_close to SIGTERM is waiting for review and responses look generally positive so far.&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&#039;&#039;&#039;Which signal?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open question:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; : the softest &amp;quot;Goodbye!&amp;quot; that a program is free to interpret as it wishes&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; : has a clear intention of &amp;quot;kill this thing&amp;quot; but still possible to trap at the target and handle in a customized way&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; : bursting with destructive potential, this signal cannot be stopped and you may not clean up&lt;br /&gt;
&lt;br /&gt;
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Discussion threads also included some notable grumbling about the Port API in general, it seems this part of ERTS is overdue for a larger redesign.  There&#039;s a good opportunity to unify the different platform implementations: Windows lacks the erl_child_setup layer entirely.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1819</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1819"/>
		<updated>2025-10-17T09:46:21Z</updated>

		<summary type="html">&lt;p&gt;Adamw: clarify&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background and can monitor progress.  I hoped to learn better how to interface with long-lived external processes—and I got more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|400x400px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, such as the static filenames—it feels unsafe to even demonstrate how string interpolation like &amp;lt;code&amp;gt;#{source}&amp;lt;/code&amp;gt; could be misused so let&#039;s skip straight to the next tool,  &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; which doesn&#039;t expand its argv:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is safer, but the calling thread loses control and gets no feedback until the transfer is complete.&lt;br /&gt;
&lt;br /&gt;
To run a external process asynchronously we reach for Elixir&#039;s lowest-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; which maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt;.  These are tremendously flexible, here we demonstrate turning a few knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines come in with a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu of alternatives:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library&#039;s &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; callback as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt; and after the transfer is finished we receive a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or even a leading carriage return as you can see in the rsync source code,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
On the terminal, rsync progress lines are updated in place by emitting a [[w:Carriage return|carriage return]] control character &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; as you see above.  The character seems to be named after pushing the physical paper carriage of a typewriter backwards without feeding a new line.  On the terminal this overwrites the current line!&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreements about carriage return]] vs. newline have caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
One more comment about this carriage return: the &amp;quot;control&amp;quot; character is just a byte in the binary data coming over the pipe from rsync, but it plays a control function because of how the tty interprets it.  Still, a repeated theme is that data and control are leaky categories.  We come to the more formal control side channels later. &lt;br /&gt;
&lt;br /&gt;
This is where Erlang/OTP really starts to shine: by opening the port inside of a dedicated gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; we have a separate thread communicating with rsync, which receives an asynchronous message like &amp;lt;code&amp;gt;{:data, text_line}&amp;lt;/code&amp;gt; for each progress line.  It&#039;s easy to parse the line, update some internal state and optionally send a progress summary to the code calling the library.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
This would have been the end of the story, but I&#039;m a very flat-footed and iterative developer and as I was calling my rsync library from my application under development, I would often kill the program abruptly by crashing or by typing &amp;lt;control&amp;gt;-C in the terminal.  Dozens of times.  What I found is that the rsync transfers would continue to run in the background even after Elixir had completely shut down.&lt;br /&gt;
&lt;br /&gt;
That would have to change—leaving overlapping file transfers running unmonitored is exactly what I wanted to avoid by having Elixir control the process in the first place.  Once the BEAM stops there was no way to clearly identify and kill the sketchy rsyncing.&lt;br /&gt;
&lt;br /&gt;
In fact, killing the lower-level threads when a higher-level supervising process dies is central to the BEAM concept of supervisors&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/sup_princ.html&amp;lt;/ref&amp;gt; which has earned the virtual machine its reputation for being legendarily robust.  Why would some external processes stop and others not?  There seemed to be no way to send a signal or close the port to stop the process, either.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: not very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, instead the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing the pipe to the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead, try it: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a real thing.  Now try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cares because it wasn&#039;t listening anway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, as it turns out &amp;quot;rsync&amp;quot; is in this latter category of programs which sees itself as a daemon which should continue even when input is closed.  This makes sense enough, since rsync expects no user input and its output is just a side-effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
BEAM assumes the connected process behaves like this, so nothing needs to be done to clean up a dangling external process because it will end itself as soon as the Port is closed or the BEAM exits.  If the external process is known to not behave this way, the recommendation is to wrap it in a shell script which converts a closed stdin into a kill signal.&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== BEAM internal and external processes ==&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
== Inside the BEAM ==&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1818</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1818"/>
		<updated>2025-10-17T09:33:26Z</updated>

		<summary type="html">&lt;p&gt;Adamw: c/e, image, formatting and arrangement&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This deceivingly simple programming adventure veers unexpectedly into piping and signaling between unix processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
{{Project|source=https://gitlab.com/adamwight/rsync_ex/|status=beta|url=https://hexdocs.pm/rsync/Rsync.html}}&lt;br /&gt;
&lt;br /&gt;
My exploration begins while writing a beta-quality rsync library for Elixir which transfers files in the background and can monitor progress.  I hoped to learn better how to interface with long-lived external processes—and I got more than I wished for.&lt;br /&gt;
&lt;br /&gt;
[[File:Monkey eating.jpg|alt=A Toque macaque (Macaca radiata) Monkey eating peanuts. Pictured in Bangalore, India|right|400x400px]]&lt;br /&gt;
&lt;br /&gt;
Starting rsync should be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a source target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings, starting with filename escaping so at a minimum we should use &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.find_executable(rsync_path)&lt;br /&gt;
|&amp;gt; System.cmd([~w(-a), source, target])&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;However this job would block until the transfer is finished and we get no feedback until completion.&lt;br /&gt;
&lt;br /&gt;
Elixir&#039;s low-level &amp;lt;code&amp;gt;Port.open&amp;lt;/code&amp;gt; maps directly to ERTS &amp;lt;code&amp;gt;open_port&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt; which provides flexibility.  Here we have a command turning some knobs:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Progress lines have a fairly self-explanatory format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
rsync has a variety of progress options, we chose overall progress above so the meaning of the percentage is &amp;quot;overall percent complete&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Here is the menu:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : report overall progress&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : report statistics per file&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; : list the operations taken on each file&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Each rsync output line is sent to the library callback &amp;lt;code&amp;gt;handle_info&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;{:data, line}&amp;lt;/code&amp;gt;, and after transfer is finished it receives a conclusive &amp;lt;code&amp;gt;{:exit_status, status_code}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Here we extract the percent_done column and strictly reject any other output:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
with terms when terms != [] &amp;lt;- String.split(line, ~r&amp;quot;\s&amp;quot;, trim: true),&lt;br /&gt;
         percent_done_text when is_binary(percent_done_text) &amp;lt;- Enum.at(terms, 1),&lt;br /&gt;
         {percent_done, &amp;quot;%&amp;quot;} &amp;lt;- Float.parse(percent_done_text) do&lt;br /&gt;
      percent_done&lt;br /&gt;
    else&lt;br /&gt;
      _ -&amp;gt;&lt;br /&gt;
        {:unknown, line}&lt;br /&gt;
    end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The &amp;lt;code&amp;gt;trim&amp;lt;/code&amp;gt; lets us ignore spacing and newline trickery—or the leading carriage return you can see in this line from rsync&#039;s source,&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
On the terminal, rsync progress lines are updated in-place by emitting the fun [[w:Carriage return|carriage return]] control character &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; as you see above.  The character seems to be named after pushing the physical paper carriage of a typewriter backwards without feeding a new line.  On the terminal this overwrites the current line!&lt;br /&gt;
&lt;br /&gt;
[[w:https://en.wikipedia.org/wiki/Newline#Issues_with_different_newline_formats|Disagreements about carriage return]] vs. newline have caused eye-rolling since the dawn of personal computing.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
One more comment about this carriage return: it&#039;s a byte in the binary data coming over the pipe from rsync, but it plays a &amp;quot;control&amp;quot; function because of how it will be interpreted by the tty.  A repeated theme is that data and control are leaky categories, &lt;br /&gt;
&lt;br /&gt;
This is where Erlang/OTP really starts to shine: by opening the port inside of a dedicated gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; we have a separate thread communicating with rsync, which receives an asynchronous message like &amp;lt;code&amp;gt;{:data, text_line}&amp;lt;/code&amp;gt; for each progress line.  It&#039;s easy to parse the line, update some internal state and optionally send a progress summary to the code calling the library.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
This would have been the end of the story, but I&#039;m a very flat-footed and iterative developer and as I was calling my rsync library from my application under development, I would often kill the program abruptly by crashing or by typing &amp;lt;control&amp;gt;-C in the terminal.  Dozens of times.  What I found is that the rsync transfers would continue to run in the background even after Elixir had completely shut down.&lt;br /&gt;
&lt;br /&gt;
That would have to change—leaving overlapping file transfers running unmonitored is exactly what I wanted to avoid by having Elixir control the process in the first place.  Once the BEAM stops there was no way to clearly identify and kill the sketchy rsyncing.&lt;br /&gt;
&lt;br /&gt;
In fact, killing the lower-level threads when a higher-level supervising process dies is central to the BEAM concept of supervisors&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/sup_princ.html&amp;lt;/ref&amp;gt; which has earned the virtual machine its reputation for being legendarily robust.  Why would some external processes stop and others not?  There seemed to be no way to send a signal or close the port to stop the process, either.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: not very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, instead the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing the pipe to the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead, try it: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a real thing.  Now try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cares because it wasn&#039;t listening anway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, as it turns out &amp;quot;rsync&amp;quot; is in this latter category of programs which sees itself as a daemon which should continue even when input is closed.  This makes sense enough, since rsync expects no user input and its output is just a side-effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
BEAM assumes the connected process behaves like this, so nothing needs to be done to clean up a dangling external process because it will end itself as soon as the Port is closed or the BEAM exits.  If the external process is known to not behave this way, the recommendation is to wrap it in a shell script which converts a closed stdin into a kill signal.&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== BEAM internal and external processes ==&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
== Inside the BEAM ==&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Template:Project&amp;diff=1817</id>
		<title>Template:Project</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Template:Project&amp;diff=1817"/>
		<updated>2025-10-17T08:47:47Z</updated>

		<summary type="html">&lt;p&gt;Adamw: url is optional&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
Examples:&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{Project|status=production|url=https://git.invalid/scrape-wiki-html-dump}}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Project|status=production|url=https://git.invalid/scrape-wiki-html-dump}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{Project|url=https://git.invalid/scrape-wiki-html-dump}}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Project|url=https://git.invalid/scrape-wiki-html-dump}}&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{Project|url=https://demo.invalid/|source=https://git.invalid/scrape-wiki-html-dump}}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Project|url=https://demo.invalid/|source=https://git.invalid/scrape-wiki-html-dump}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;clear: both;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;templatedata&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;description&amp;quot;: &amp;quot;Infobox about the project documented on this page.&amp;quot;,&lt;br /&gt;
	&amp;quot;params&amp;quot;: {&lt;br /&gt;
		&amp;quot;status&amp;quot;: {&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;url&amp;quot;: {&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;url&amp;quot;&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;source&amp;quot;: {&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;url&amp;quot;&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/templatedata&amp;gt;&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&amp;lt;includeonly&amp;gt;&lt;br /&gt;
&amp;lt;div style=&amp;quot;float: right; clear: right; display:flex; flex-direction: row&amp;quot;&amp;gt;&lt;br /&gt;
	&amp;lt;div style=&amp;quot;margin-top: auto; margin-bottom: auto&amp;quot;&amp;gt;[[File:Git format.png|64x64px]]&amp;lt;/div&amp;gt;&lt;br /&gt;
	&amp;lt;div style=&amp;quot;margin-top: auto; margin-bottom: auto&amp;quot;&amp;gt;&lt;br /&gt;
		Project link{{#if: {{{status|}}} | &amp;amp;nbsp;({{{status|}}}) | }}:&lt;br /&gt;
		&amp;lt;br&amp;gt;&lt;br /&gt;
		{{{url|}}}&lt;br /&gt;
		{{#if: {{{source|}}} | &amp;lt;br&amp;gt;Source&amp;amp;nbsp;code:&amp;lt;br&amp;gt;{{{source}}} }}&lt;br /&gt;
	&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/includeonly&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1816</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1816"/>
		<updated>2025-10-16T22:27:39Z</updated>

		<summary type="html">&lt;p&gt;Adamw: c/e and move out some asides&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This is a short programming adventure which goes into piping and signaling between processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
This exploration began with writing a library&amp;lt;ref&amp;gt;https://hexdocs.pm/rsync/Rsync.html&amp;lt;/ref&amp;gt; to run rsync in order to transfer files in a background thread and monitor progress.  I hoped to learn how to interface with long-lived external processes, and I got more than I wished for.&lt;br /&gt;
&lt;br /&gt;
Starting rsync would be as easy as calling out to a shell:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
System.shell(&amp;quot;rsync -a src target&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This has a few shortcomings: filename escaping is hard to do safely so &amp;lt;code&amp;gt;System.cmd&amp;lt;/code&amp;gt; should be used instead, and the job would block until the transfer is done so we get no feedback until completion.  Ending the shell command in an ampersand &amp;lt;code&amp;gt;&amp;amp;&amp;lt;/code&amp;gt; is not enough, so the caller would have to manually start a new thread.&lt;br /&gt;
&lt;br /&gt;
Elixir&#039;s low-level &amp;lt;code&amp;gt;Port&amp;lt;/code&amp;gt; call maps directly to the base Erlang open_port&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt; and it gives much more flexibility:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
If you&#039;re here for rsync, it includes a few alternatives for progress reporting:&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; : reports overall progress&lt;br /&gt;
; &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; : reports statistics per file&lt;br /&gt;
; &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; ; lists the operations taken on each file&lt;br /&gt;
&lt;br /&gt;
Progress reporting uses a columnar format:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Aside|text=&lt;br /&gt;
On the terminal the progress line is updated in-place by restarting the line with the fun [[w:Carriage return|carriage return]] control character &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;.  This is apparently named after pushing the physical paper carriage of a typewriter and on a terminal it will erases the current line so it can be written again!  But over a pipe we see this as a regular byte in the stream, like &amp;quot;&amp;lt;code&amp;gt;-old line-^M-new line-&amp;lt;/code&amp;gt;&amp;quot;.  [[W:|Disagreements]] about carriage return vs. newline have caused eye-rolling since the dawn of personal computing but we can double-check the rsync source code and we see that it will format output using carriage return on any platform: &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
This is where Erlang/OTP really starts to shine: by opening the port inside of a dedicated gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; we have a separate thread communicating with rsync, which receives an asynchronous message like &amp;lt;code&amp;gt;{:data, text_line}&amp;lt;/code&amp;gt; for each progress line.  It&#039;s easy to parse the line, update some internal state and optionally send a progress summary to the code calling the library.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
This would have been the end of the story, but I&#039;m a very flat-footed and iterative developer and as I was calling my rsync library from my application under development, I would often kill the program abruptly by crashing or by typing &amp;lt;control&amp;gt;-C in the terminal.  Dozens of times.  What I found is that the rsync transfers would continue to run in the background even after Elixir had completely shut down.&lt;br /&gt;
&lt;br /&gt;
That would have to change—leaving overlapping file transfers running unmonitored is exactly what I wanted to avoid by having Elixir control the process in the first place.  Once the BEAM stops there was no way to clearly identify and kill the sketchy rsyncing.&lt;br /&gt;
&lt;br /&gt;
In fact, killing the lower-level threads when a higher-level supervising process dies is central to the BEAM concept of supervisors&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/sup_princ.html&amp;lt;/ref&amp;gt; which has earned the virtual machine its reputation for being legendarily robust.  Why would some external processes stop and others not?  There seemed to be no way to send a signal or close the port to stop the process, either.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: not very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, instead the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing the pipe to the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead, try it: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a real thing.  Now try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cares because it wasn&#039;t listening anway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, as it turns out &amp;quot;rsync&amp;quot; is in this latter category of programs which sees itself as a daemon which should continue even when input is closed.  This makes sense enough, since rsync expects no user input and its output is just a side-effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
BEAM assumes the connected process behaves like this, so nothing needs to be done to clean up a dangling external process because it will end itself as soon as the Port is closed or the BEAM exits.  If the external process is known to not behave this way, the recommendation is to wrap it in a shell script which converts a closed stdin into a kill signal.&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== BEAM internal and external processes ==&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
== Inside the BEAM ==&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Module:Message_box/ambox.css&amp;diff=1815</id>
		<title>Module:Message box/ambox.css</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Module:Message_box/ambox.css&amp;diff=1815"/>
		<updated>2025-10-16T22:11:53Z</updated>

		<summary type="html">&lt;p&gt;Adamw: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;.ambox {&lt;br /&gt;
	border: 1px solid #a2a9b1;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #36c;  /* Default &amp;quot;notice&amp;quot; blue */&lt;br /&gt;
	/*background-color: #fbfbfb;*/&lt;br /&gt;
	box-sizing: border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Single border between stacked boxes. Take into account base templatestyles,&lt;br /&gt;
 * user styles, and Template:Dated maintenance category.&lt;br /&gt;
 * remove link selector when T200206 is fixed&lt;br /&gt;
 */&lt;br /&gt;
.ambox + link + .ambox,&lt;br /&gt;
.ambox + link + style + .ambox,&lt;br /&gt;
.ambox + link + link + .ambox,&lt;br /&gt;
/* TODO: raise these as &amp;quot;is this really that necessary???&amp;quot;. the change was Dec 2021 */&lt;br /&gt;
.ambox + .mw-empty-elt + link + .ambox,&lt;br /&gt;
.ambox + .mw-empty-elt + link + style + .ambox,&lt;br /&gt;
.ambox + .mw-empty-elt + link + link + .ambox {&lt;br /&gt;
	margin-top: -1px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* For the &amp;quot;small=left&amp;quot; option. */&lt;br /&gt;
/* must override .ambox + .ambox styles above */&lt;br /&gt;
html body.mediawiki .ambox.mbox-small-left {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	margin: 4px 1em 4px 0;&lt;br /&gt;
	overflow: hidden;&lt;br /&gt;
	width: 238px;&lt;br /&gt;
	border-collapse: collapse;&lt;br /&gt;
	font-size: 88%;&lt;br /&gt;
	line-height: 1.25em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-speedy {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #b32424;    /* Red */&lt;br /&gt;
	background-color: #fee7e6;          /* Pink */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-delete {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #b32424;    /* Red */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-content {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #f28500;    /* Orange */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-style {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #fc3;       /* Yellow */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-move {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #9932cc;    /* Purple */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-protection {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #a2a9b1;    /* Gray-gold */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-text {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 0.25em 0.5em;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-image {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 2px 0 2px 0.5em;&lt;br /&gt;
	text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-imageright {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 2px 0.5em 2px 0;&lt;br /&gt;
	text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* An empty narrow cell */&lt;br /&gt;
.ambox .mbox-empty-cell {&lt;br /&gt;
	border: none;&lt;br /&gt;
	padding: 0;&lt;br /&gt;
	width: 1px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-image-div {&lt;br /&gt;
	width: 52px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (min-width: 720px) {&lt;br /&gt;
	.ambox {&lt;br /&gt;
		margin: 0 10%;                  /* 10% = Will not overlap with other elements */&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
/*&lt;br /&gt;
@media print {&lt;br /&gt;
	body.ns-0 .ambox {&lt;br /&gt;
		display: none !important;&lt;br /&gt;
	}&lt;br /&gt;
}*/&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Module:Message_box/ambox.css&amp;diff=1814</id>
		<title>Module:Message box/ambox.css</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Module:Message_box/ambox.css&amp;diff=1814"/>
		<updated>2025-10-16T22:10:56Z</updated>

		<summary type="html">&lt;p&gt;Adamw: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* {{pp|small=y}} */&lt;br /&gt;
.ambox {&lt;br /&gt;
	border: 1px solid #a2a9b1;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #36c;  /* Default &amp;quot;notice&amp;quot; blue */&lt;br /&gt;
	/*background-color: #fbfbfb;*/&lt;br /&gt;
	box-sizing: border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Single border between stacked boxes. Take into account base templatestyles,&lt;br /&gt;
 * user styles, and Template:Dated maintenance category.&lt;br /&gt;
 * remove link selector when T200206 is fixed&lt;br /&gt;
 */&lt;br /&gt;
.ambox + link + .ambox,&lt;br /&gt;
.ambox + link + style + .ambox,&lt;br /&gt;
.ambox + link + link + .ambox,&lt;br /&gt;
/* TODO: raise these as &amp;quot;is this really that necessary???&amp;quot;. the change was Dec 2021 */&lt;br /&gt;
.ambox + .mw-empty-elt + link + .ambox,&lt;br /&gt;
.ambox + .mw-empty-elt + link + style + .ambox,&lt;br /&gt;
.ambox + .mw-empty-elt + link + link + .ambox {&lt;br /&gt;
	margin-top: -1px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* For the &amp;quot;small=left&amp;quot; option. */&lt;br /&gt;
/* must override .ambox + .ambox styles above */&lt;br /&gt;
html body.mediawiki .ambox.mbox-small-left {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	margin: 4px 1em 4px 0;&lt;br /&gt;
	overflow: hidden;&lt;br /&gt;
	width: 238px;&lt;br /&gt;
	border-collapse: collapse;&lt;br /&gt;
	font-size: 88%;&lt;br /&gt;
	line-height: 1.25em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-speedy {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #b32424;    /* Red */&lt;br /&gt;
	background-color: #fee7e6;          /* Pink */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-delete {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #b32424;    /* Red */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-content {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #f28500;    /* Orange */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-style {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #fc3;       /* Yellow */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-move {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #9932cc;    /* Purple */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-protection {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #a2a9b1;    /* Gray-gold */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-text {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 0.25em 0.5em;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-image {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 2px 0 2px 0.5em;&lt;br /&gt;
	text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-imageright {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 2px 0.5em 2px 0;&lt;br /&gt;
	text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* An empty narrow cell */&lt;br /&gt;
.ambox .mbox-empty-cell {&lt;br /&gt;
	border: none;&lt;br /&gt;
	padding: 0;&lt;br /&gt;
	width: 1px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-image-div {&lt;br /&gt;
	width: 52px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (min-width: 720px) {&lt;br /&gt;
	.ambox {&lt;br /&gt;
		margin: 0 10%;                  /* 10% = Will not overlap with other elements */&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
/*&lt;br /&gt;
@media print {&lt;br /&gt;
	body.ns-0 .ambox {&lt;br /&gt;
		display: none !important;&lt;br /&gt;
	}&lt;br /&gt;
}*/&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1813</id>
		<title>Template:Aside</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1813"/>
		<updated>2025-10-16T22:06:20Z</updated>

		<summary type="html">&lt;p&gt;Adamw: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;includeonly&amp;gt;&lt;br /&gt;
&amp;lt;templatestyles src=&amp;quot;Module:Message box/ambox.css&amp;quot;&amp;gt;&amp;lt;/templatestyles&amp;gt;&lt;br /&gt;
&amp;lt;table class=&amp;quot;box-reltime plainlinks metadata ambox ambox-notice&amp;quot; role=&amp;quot;presentation&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;tr&amp;gt;&lt;br /&gt;
    &amp;lt;td class=&amp;quot;mbox-image&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;div class=&amp;quot;mbox-image-div&amp;quot;&amp;gt;[[File:Information icon4.svg|40x40px|link=|alt=]]&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/td&amp;gt;&lt;br /&gt;
    &amp;lt;td class=&amp;quot;mbox-text&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;div class=&amp;quot;mbox-text-span&amp;quot;&amp;gt;{{{text|}}}&amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/td&amp;gt;&lt;br /&gt;
  &amp;lt;/tr&amp;gt;&lt;br /&gt;
&amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
Example: &amp;lt;pre&amp;gt;{{Aside|text=Content}}&amp;lt;/pre&amp;gt; {{Aside|text=Content}}&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1812</id>
		<title>Template:Aside</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1812"/>
		<updated>2025-10-16T22:03:14Z</updated>

		<summary type="html">&lt;p&gt;Adamw: include templatestyles&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;includeonly&amp;gt;&amp;lt;templatestyles src=&amp;quot;Module:Message box/ambox.css&amp;quot;&amp;gt;&amp;lt;/templatestyles&amp;gt;&amp;lt;table class=&amp;quot;box-reltime plainlinks metadata ambox ambox-notice&amp;quot; role=&amp;quot;presentation&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td class=&amp;quot;mbox-image&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;mbox-image-div&amp;quot;&amp;gt;[[File:Information icon4.svg|40x40px|link=|alt=]]&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td class=&amp;quot;mbox-text&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;mbox-text-span&amp;quot;&amp;gt;{{{text|}}}&amp;lt;span class=&amp;quot;hide-when-compact&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
Example: &amp;lt;pre&amp;gt;{{Aside|text=Content}}&amp;lt;/pre&amp;gt; {{Aside|text=Content}}&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Module:Message_box/ambox.css&amp;diff=1811</id>
		<title>Module:Message box/ambox.css</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Module:Message_box/ambox.css&amp;diff=1811"/>
		<updated>2025-10-16T22:02:07Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Created page with &amp;quot;/* {{pp|small=y}} */ .ambox { 	border: 1px solid #a2a9b1; 	/* @noflip */ 	border-left: 10px solid #36c;  /* Default &amp;quot;notice&amp;quot; blue */ 	background-color: #fbfbfb; 	box-sizing: border-box; }  /* Single border between stacked boxes. Take into account base templatestyles,  * user styles, and Template:Dated maintenance category.  * remove link selector when T200206 is fixed  */ .ambox + link + .ambox, .ambox + link + style + .ambox, .ambox + link + link + .ambox, /* TODO: rais...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* {{pp|small=y}} */&lt;br /&gt;
.ambox {&lt;br /&gt;
	border: 1px solid #a2a9b1;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #36c;  /* Default &amp;quot;notice&amp;quot; blue */&lt;br /&gt;
	background-color: #fbfbfb;&lt;br /&gt;
	box-sizing: border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Single border between stacked boxes. Take into account base templatestyles,&lt;br /&gt;
 * user styles, and Template:Dated maintenance category.&lt;br /&gt;
 * remove link selector when T200206 is fixed&lt;br /&gt;
 */&lt;br /&gt;
.ambox + link + .ambox,&lt;br /&gt;
.ambox + link + style + .ambox,&lt;br /&gt;
.ambox + link + link + .ambox,&lt;br /&gt;
/* TODO: raise these as &amp;quot;is this really that necessary???&amp;quot;. the change was Dec 2021 */&lt;br /&gt;
.ambox + .mw-empty-elt + link + .ambox,&lt;br /&gt;
.ambox + .mw-empty-elt + link + style + .ambox,&lt;br /&gt;
.ambox + .mw-empty-elt + link + link + .ambox {&lt;br /&gt;
	margin-top: -1px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* For the &amp;quot;small=left&amp;quot; option. */&lt;br /&gt;
/* must override .ambox + .ambox styles above */&lt;br /&gt;
html body.mediawiki .ambox.mbox-small-left {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	margin: 4px 1em 4px 0;&lt;br /&gt;
	overflow: hidden;&lt;br /&gt;
	width: 238px;&lt;br /&gt;
	border-collapse: collapse;&lt;br /&gt;
	font-size: 88%;&lt;br /&gt;
	line-height: 1.25em;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-speedy {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #b32424;    /* Red */&lt;br /&gt;
	background-color: #fee7e6;          /* Pink */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-delete {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #b32424;    /* Red */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-content {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #f28500;    /* Orange */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-style {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #fc3;       /* Yellow */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-move {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #9932cc;    /* Purple */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox-protection {&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	border-left: 10px solid #a2a9b1;    /* Gray-gold */&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-text {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 0.25em 0.5em;&lt;br /&gt;
	width: 100%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-image {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 2px 0 2px 0.5em;&lt;br /&gt;
	text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-imageright {&lt;br /&gt;
	border: none;&lt;br /&gt;
	/* @noflip */&lt;br /&gt;
	padding: 2px 0.5em 2px 0;&lt;br /&gt;
	text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* An empty narrow cell */&lt;br /&gt;
.ambox .mbox-empty-cell {&lt;br /&gt;
	border: none;&lt;br /&gt;
	padding: 0;&lt;br /&gt;
	width: 1px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.ambox .mbox-image-div {&lt;br /&gt;
	width: 52px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (min-width: 720px) {&lt;br /&gt;
	.ambox {&lt;br /&gt;
		margin: 0 10%;                  /* 10% = Will not overlap with other elements */&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
/*&lt;br /&gt;
@media print {&lt;br /&gt;
	body.ns-0 .ambox {&lt;br /&gt;
		display: none !important;&lt;br /&gt;
	}&lt;br /&gt;
}*/&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1809</id>
		<title>Template:Aside</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1809"/>
		<updated>2025-10-16T20:45:42Z</updated>

		<summary type="html">&lt;p&gt;Adamw: copy rendered ambox&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;includeonly&amp;gt;&amp;lt;table class=&amp;quot;box-reltime plainlinks metadata ambox ambox-notice&amp;quot; role=&amp;quot;presentation&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td class=&amp;quot;mbox-image&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;mbox-image-div&amp;quot;&amp;gt;[[File:Information icon4.svg|40x40px|link=|alt=]]&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td class=&amp;quot;mbox-text&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;mbox-text-span&amp;quot;&amp;gt;{{{text|}}}&amp;lt;span class=&amp;quot;hide-when-compact&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/table&amp;gt;&lt;br /&gt;
&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
Example: &amp;lt;pre&amp;gt;{{Aside|text=Content}}&amp;lt;/pre&amp;gt; {{Aside|text=Content}}&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1808</id>
		<title>Template:Aside</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Template:Aside&amp;diff=1808"/>
		<updated>2025-10-16T20:43:15Z</updated>

		<summary type="html">&lt;p&gt;Adamw: copied from expanded template&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;table class=&amp;quot;box-reltime plainlinks metadata ambox ambox-notice&amp;quot; role=&amp;quot;presentation&amp;quot;&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;td class=&amp;quot;mbox-image&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;mbox-image-div&amp;quot;&amp;gt;[[File:Information icon4.svg|40x40px|link=|alt=]]&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td class=&amp;quot;mbox-text&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;mbox-text-span&amp;quot;&amp;gt;{{{text|}}}&amp;lt;span class=&amp;quot;hide-when-compact&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/table&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Draft:Elixir/OS_processes&amp;diff=1805</id>
		<title>Draft:Elixir/OS processes</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Draft:Elixir/OS_processes&amp;diff=1805"/>
		<updated>2025-10-16T20:31:31Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Adamw moved page Draft:Elixir/OS processes to Draft:Elixir/Ports and external process wiring&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Draft:Elixir/Ports and external process wiring]]&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1804</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1804"/>
		<updated>2025-10-16T20:31:31Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Adamw moved page Draft:Elixir/OS processes to Draft:Elixir/Ports and external process wiring&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This is a short programming adventure which goes into piping and signaling between processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
This exploration began when I wrote a simple library to run rsync from an Elixir program&amp;lt;ref&amp;gt;https://hexdocs.pm/rsync/Rsync.html&amp;lt;/ref&amp;gt;, to transfer files in a background thread while monitoring progress.  I was hoping to learn how to interface with long-lived external processes, and I ended up learning more than I wished for.&lt;br /&gt;
&lt;br /&gt;
Starting rsync and reading from it went very well, mostly thanks to the &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; option which reports progress with a simple columnar format that can be easily parsed:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;In case you&#039;re here to integrate with rsync, there&#039;s also a slightly different &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; option which reports statistics per file, and an option &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; which can be included to get information about the operations taken on each file, but in my case I care more about the overall transfer progress.&lt;br /&gt;
&lt;br /&gt;
On the terminal the progress line is updated in-place by restarting the line with the fun [[w:Carriage return|carriage return]] control character &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;.  This is apparently named after pushing the physical paper carriage of a typewriter and on a terminal it will erases the current line so it can be written again!  But over a pipe we see this as a regular byte in the stream, like &amp;quot;&amp;lt;code&amp;gt;-old line-^M-new line-&amp;lt;/code&amp;gt;&amp;quot;.  [[W:|Disagreements]] about carriage return vs. newline have caused eye-rolling since the dawn of personal computing but we can double-check the rsync source code and we see that it will format output using carriage return on any platform: &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
My library starts rsync using Elixir&#039;s low-level &amp;lt;code&amp;gt;Port&amp;lt;/code&amp;gt; call, which maps directly to the base Erlang open_port&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt; implementation:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is where Erlang/OTP really starts to shine: by opening the port inside of a dedicated gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; we have a separate thread communicating with rsync, which receives an asynchronous message like &amp;lt;code&amp;gt;{:data, text_line}&amp;lt;/code&amp;gt; for each progress line.  It&#039;s easy to parse the line, update some internal state and optionally send a progress summary to the code calling the library.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
This would have been the end of the story, but I&#039;m a very flat-footed and iterative developer and as I was calling my rsync library from my application under development, I would often kill the program abruptly by crashing or by typing &amp;lt;control&amp;gt;-C in the terminal.  Dozens of times.  What I found is that the rsync transfers would continue to run in the background even after Elixir had completely shut down.&lt;br /&gt;
&lt;br /&gt;
That would have to change—leaving overlapping file transfers running unmonitored is exactly what I wanted to avoid by having Elixir control the process in the first place.  Once the BEAM stops there was no way to clearly identify and kill the sketchy rsyncing.&lt;br /&gt;
&lt;br /&gt;
In fact, killing the lower-level threads when a higher-level supervising process dies is central to the BEAM concept of supervisors&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/sup_princ.html&amp;lt;/ref&amp;gt; which has earned the virtual machine its reputation for being legendarily robust.  Why would some external processes stop and others not?  There seemed to be no way to send a signal or close the port to stop the process, either.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: not very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, instead the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing the pipe to the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead, try it: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a real thing.  Now try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cares because it wasn&#039;t listening anway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, as it turns out &amp;quot;rsync&amp;quot; is in this latter category of programs which sees itself as a daemon which should continue even when input is closed.  This makes sense enough, since rsync expects no user input and its output is just a side-effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
BEAM assumes the connected process behaves like this, so nothing needs to be done to clean up a dangling external process because it will end itself as soon as the Port is closed or the BEAM exits.  If the external process is known to not behave this way, the recommendation is to wrap it in a shell script which converts a closed stdin into a kill signal.&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== BEAM internal and external processes ==&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
== Inside the BEAM ==&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1801</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1801"/>
		<updated>2025-10-16T17:12:13Z</updated>

		<summary type="html">&lt;p&gt;Adamw: lots of background detail&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This is a short programming adventure which goes into piping and signaling between processes.&lt;br /&gt;
&lt;br /&gt;
== Context: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
This exploration began when I wrote a simple library to run rsync from an Elixir program&amp;lt;ref&amp;gt;https://hexdocs.pm/rsync/Rsync.html&amp;lt;/ref&amp;gt;, to transfer files in a background thread while monitoring progress.  I was hoping to learn how to interface with long-lived external processes, and I ended up learning more than I wished for.&lt;br /&gt;
&lt;br /&gt;
Starting rsync and reading from it went very well, mostly thanks to the &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; option which reports progress with a simple columnar format that can be easily parsed:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
      3,342,336  33%    3.14MB/s    0:00:02&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;In case you&#039;re here to integrate with rsync, there&#039;s also a slightly different &amp;lt;code&amp;gt;--progress&amp;lt;/code&amp;gt; option which reports statistics per file, and an option &amp;lt;code&amp;gt;--itemize-changes&amp;lt;/code&amp;gt; which can be included to get information about the operations taken on each file, but in my case I care more about the overall transfer progress.&lt;br /&gt;
&lt;br /&gt;
On the terminal the progress line is updated in-place by restarting the line with the fun [[w:Carriage return|carriage return]] control character &amp;lt;code&amp;gt;0x0d&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;.  This is apparently named after pushing the physical paper carriage of a typewriter and on a terminal it will erases the current line so it can be written again!  But over a pipe we see this as a regular byte in the stream, like &amp;quot;&amp;lt;code&amp;gt;-old line-^M-new line-&amp;lt;/code&amp;gt;&amp;quot;.  [[W:|Disagreements]] about carriage return vs. newline have caused eye-rolling since the dawn of personal computing but we can double-check the rsync source code and we see that it will format output using carriage return on any platform: &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
rprintf(FCLIENT, &amp;quot;\r%15s %3d%% %7.2f%s %s%s&amp;quot;, ...);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
My library starts rsync using Elixir&#039;s low-level &amp;lt;code&amp;gt;Port&amp;lt;/code&amp;gt; call, which maps directly to the base Erlang open_port&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt; implementation:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is where Erlang/OTP really starts to shine: by opening the port inside of a dedicated gen_server&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/stdlib/gen_server.html&amp;lt;/ref&amp;gt; we have a separate thread communicating with rsync, which receives an asynchronous message like &amp;lt;code&amp;gt;{:data, text_line}&amp;lt;/code&amp;gt; for each progress line.  It&#039;s easy to parse the line, update some internal state and optionally send a progress summary to the code calling the library.&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
This would have been the end of the story, but I&#039;m a very flat-footed and iterative developer and as I was calling my rsync library from my application under development, I would often kill the program abruptly by crashing or by typing &amp;lt;control&amp;gt;-C in the terminal.  Dozens of times.  What I found is that the rsync transfers would continue to run in the background even after Elixir had completely shut down.&lt;br /&gt;
&lt;br /&gt;
That would have to change—leaving overlapping file transfers running unmonitored is exactly what I wanted to avoid by having Elixir control the process in the first place.  Once the BEAM stops there was no way to clearly identify and kill the sketchy rsyncing.&lt;br /&gt;
&lt;br /&gt;
In fact, killing the lower-level threads when a higher-level supervising process dies is central to the BEAM concept of supervisors&amp;lt;ref&amp;gt;https://www.erlang.org/doc/system/sup_princ.html&amp;lt;/ref&amp;gt; which has earned the virtual machine its reputation for being legendarily robust.  Why would some external processes stop and others not?  There seemed to be no way to send a signal or close the port to stop the process, either.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A straightforward use case for external processes would be to run a standard transformation such as compression or decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, because the main loop usually makes a C system call to &amp;lt;code&amp;gt;read&amp;lt;/code&amp;gt; like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.  If you think this sounds weird, I would agree: how do we tell the difference between a stream which is stalled and one which has ended?  Does the calling process yield control until input arrives?  How do we know if more than bufsize bytes are available?  If that word salad excites you, read more about &amp;lt;code&amp;gt;O_NONBLOCK&amp;lt;/code&amp;gt;&amp;lt;ref&amp;gt;https://man.archlinux.org/man/open.2.en#O_NONBLOCK&amp;lt;/ref&amp;gt; and unix pipes&amp;lt;ref&amp;gt;https://man.archlinux.org/man/pipe.7.en&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
But here we&#039;ll focus on how processes affect each other through pipes.  Surprising answer: not very much!  Try opening a &amp;quot;cat&amp;quot; in the terminal and then type &amp;lt;control&amp;gt;-d to &amp;quot;send&amp;quot; an end-of-file.  Oh no, you killed it!  You didn&#039;t actually send anything, instead the &amp;lt;control&amp;gt;-d is interpreted by bash and it responds by closing the pipe to the child process.  This is similar to how &amp;lt;control&amp;gt;-c is not sending a character but is interpreted by the terminal, trapped by the shell and forwarded as an interrupt signal to the child process, completely independently of the data pipe.  My entry point to learning more is this stty webzine&amp;lt;ref&amp;gt;https://wizardzines.com/comics/stty/&amp;lt;/ref&amp;gt; by Julia Evans.  Go ahead, try it: &amp;lt;code&amp;gt;stty -a&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Any special behavior at the other end of a pipe is the result of intentional programming decisions and &amp;quot;end of file&amp;quot; (EOF) is more a convention than a real thing.  Now try opening &amp;quot;watch ls&amp;quot; or &amp;quot;sleep 60&amp;quot; and try &amp;lt;control&amp;gt;-d all you want—no effect.  You did close its stdin but nobody cares because it wasn&#039;t listening anway.&lt;br /&gt;
&lt;br /&gt;
Back to the problem at hand, as it turns out &amp;quot;rsync&amp;quot; is in this latter category of programs which sees itself as a daemon which should continue even when input is closed.  This makes sense enough, since rsync expects no user input and its output is just a side-effect of its main purpose.&lt;br /&gt;
&lt;br /&gt;
BEAM assumes the connected process behaves like this, so nothing needs to be done to clean up a dangling external process because it will end itself as soon as the Port is closed or the BEAM exits.  If the external process is known to not behave this way, the recommendation is to wrap it in a shell script which converts a closed stdin into a kill signal.&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== BEAM internal and external processes ==&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
== Inside the BEAM ==&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1800</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1800"/>
		<updated>2025-10-16T07:00:03Z</updated>

		<summary type="html">&lt;p&gt;Adamw: increase all heading levels&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Challenge: controlling &amp;quot;rsync&amp;quot; ==&lt;br /&gt;
This exploration began as I wrote a simple library to run rsync from Elixir.&amp;lt;ref&amp;gt;https://hexdocs.pm/rsync/Rsync.html&amp;lt;/ref&amp;gt;  I was hoping to learn how to interface with long-lived external processes, in this case to transfer files and monitor progress.  Starting and reading from rsync went very well, thanks to the &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; option which reports progress in a fairly machine-readable format.  I was able to start the file transfer, capture status, and report it back to the Elixir caller in various ways.&lt;br /&gt;
&lt;br /&gt;
My library starts rsync using a low-level &amp;lt;code&amp;gt;Port&amp;lt;/code&amp;gt; call, which maps directly to the base Erlang open_port&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt; implementation:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Problem: runaway processes ==&lt;br /&gt;
Since I was calling my rsync library from an application under development, I would often kill the program abruptly by crashing or by typing &amp;lt;control&amp;gt;-C in the terminal.  What I found is that the rsync transfer would continue to run in the background even after Elixir had completely shut down.&lt;br /&gt;
&lt;br /&gt;
That would have to change—leaving overlapping file transfers running unmonitored is exactly what I wanted to avoid by having Elixir control the process in the first place.&lt;br /&gt;
&lt;br /&gt;
== Bad assumption: pipe-like processes ==&lt;br /&gt;
A common use case is to use external processes for something like compression and decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, using a C system call like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.&lt;br /&gt;
&lt;br /&gt;
BEAM assumes the connected process behaves like this, so nothing needs to be done to clean up a dangling external process because it will end itself as soon as the Port is closed or the BEAM exits.  If the external process is known to not behave this way, the recommendation is to wrap it in a shell script which converts a closed stdin into a kill signal.&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== BEAM internal and external processes ==&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
== Reliable clean up ==&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
== Inside the BEAM ==&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1799</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=1799"/>
		<updated>2025-10-16T06:27:40Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Add some introduction&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==== Challenge: controlling &amp;quot;rsync&amp;quot; ====&lt;br /&gt;
This exploration began as I wrote a simple library to run rsync from Elixir.&amp;lt;ref&amp;gt;https://hexdocs.pm/rsync/Rsync.html&amp;lt;/ref&amp;gt;  I was hoping to learn how to interface with long-lived external processes, in this case to transfer files and monitor progress.  Starting and reading from rsync went very well, thanks to the &amp;lt;code&amp;gt;--info=progress2&amp;lt;/code&amp;gt; option which reports progress in a fairly machine-readable format.  I was able to start the file transfer, capture status, and report it back to the Elixir caller in various ways.&lt;br /&gt;
&lt;br /&gt;
My library starts rsync using a low-level &amp;lt;code&amp;gt;Port&amp;lt;/code&amp;gt; call, which maps directly to the base Erlang open_port&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erlang.html#open_port/2&amp;lt;/ref&amp;gt; implementation:&amp;lt;syntaxhighlight lang=&amp;quot;elixir&amp;quot;&amp;gt;&lt;br /&gt;
Port.open(&lt;br /&gt;
  {:spawn_executable, rsync_path},&lt;br /&gt;
  [&lt;br /&gt;
    :binary,&lt;br /&gt;
    :exit_status,&lt;br /&gt;
    :hide,&lt;br /&gt;
    :use_stdio,&lt;br /&gt;
    :stderr_to_stdout,&lt;br /&gt;
    args:&lt;br /&gt;
      ~w(-a --info=progress2) ++&lt;br /&gt;
        rsync_args ++&lt;br /&gt;
        sources ++&lt;br /&gt;
        [args[:target]],&lt;br /&gt;
    env: env&lt;br /&gt;
  ]&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Problem: runaway processes ====&lt;br /&gt;
Since I was calling my rsync library from an application under development, I would often kill the program abruptly by crashing or by typing &amp;lt;control&amp;gt;-C in the terminal.  What I found is that the rsync transfer would continue to run in the background even after Elixir had completely shut down.&lt;br /&gt;
&lt;br /&gt;
That would have to change—leaving overlapping file transfers running unmonitored is exactly what I wanted to avoid by having Elixir control the process in the first place.&lt;br /&gt;
&lt;br /&gt;
==== Bad assumption: pipe-like processes ====&lt;br /&gt;
A common use case is to use external processes for something like compression and decompression.  A program like &amp;lt;code&amp;gt;gzip&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt; will stop once it detects that its input has ended, using a C system call like this:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
ssize_t n_read = read (input_desc, buf, bufsize);&lt;br /&gt;
if (n_read &amp;lt; 0) { error... }&lt;br /&gt;
if (n_read == 0) { end of file... }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The manual for read&amp;lt;ref&amp;gt;https://man.archlinux.org/man/read.2&amp;lt;/ref&amp;gt; explains that reading 0 bytes indicates the end of file, and a negative number indicates an error such as the input file descriptor already being closed.&lt;br /&gt;
&lt;br /&gt;
BEAM assumes the connected process behaves like this, so nothing needs to be done to clean up a dangling external process because it will end itself as soon as the Port is closed or the BEAM exits.  If the external process is known to not behave this way, the recommendation is to wrap it in a shell script which converts a closed stdin into a kill signal.&amp;lt;ref&amp;gt;https://hexdocs.pm/elixir/main/Port.html#module-orphan-operating-system-processes&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== BEAM internal and external processes ====&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
==== Reliable clean up ====&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
==== Inside the BEAM ====&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=181</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=181"/>
		<updated>2025-03-11T08:00:29Z</updated>

		<summary type="html">&lt;p&gt;Adamw: link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==== BEAM internal and external processes ====&lt;br /&gt;
[[W:BEAM (Erlang virtual machine)|BEAM]] applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
==== Reliable clean up ====&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
==== Inside the BEAM ====&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=180</id>
		<title>Elixir/Ports and external process wiring</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Elixir/Ports_and_external_process_wiring&amp;diff=180"/>
		<updated>2025-03-10T16:25:03Z</updated>

		<summary type="html">&lt;p&gt;Adamw: early draft&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==== BEAM internal and external processes ====&lt;br /&gt;
&amp;quot;beam&amp;quot; applications are built out of supervision trees and excel at managing huge numbers of parallel actor processes, all scheduled internally.  Although the communities&#039; mostly share a philosophy of running as much as possible inside of the VM because it builds on this strength, and simplifies away much interface glue and context switching, on many occasions it will still start an external OS process.  There are some straightforward ways to simply run a command line, which might be familiar to programmers coming from another language: &amp;lt;code&amp;gt;[https://www.erlang.org/doc/apps/kernel/os.html#cmd/2 os:cmd]&amp;lt;/code&amp;gt; takes a string and runs the thing.  At a lower level, external programs are managed through a [https://www.erlang.org/doc/system/ports.html Port] which is a flexible abstraction allowing a backend driver to communicate data in and out, and to send some control signals such as reporting an external process&#039;s exit and exit status.&lt;br /&gt;
&lt;br /&gt;
When it comes to internal processes, BEAM is among the most mature and robust, achieved by good isolation and by its hierarchical [https://www.erlang.org/doc/system/sup_princ supervisors] liberally pruning entire subprocess trees at the first sign of going out of specification.  But for external processes, results are mixed.  Some programs are twitchy and crash easily, for example &amp;lt;code&amp;gt;cat&amp;lt;/code&amp;gt;, but others like the BEAM itself or a long-running server are built to survive any ordinary I/O glitch or accidental mashing of the keyboard.  Furthermore, this will usually be a fundamental assumption of that program and there will be no configuration to make the program behave differently depending on stimulus.&lt;br /&gt;
&lt;br /&gt;
==== Reliable clean up ====&lt;br /&gt;
What I discovered is that the BEAM external process library assumes that its spawned processes will respond to standard input and output shutting down or so called end of file, for example what happens when &amp;lt;control&amp;gt;-d is typed into the shell.  This works very well for a subprocess like &amp;lt;code&amp;gt;bash&amp;lt;/code&amp;gt; but has no effect on a program like &amp;lt;code&amp;gt;sleep&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;rsync&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The hole created by this mismatch is interestingly solved by something shaped like the BEAM&#039;s supervisor itself.  I would expect the VM to spawn many processes as necessary, but I wouldn&#039;t expect the child process to outlive the VM, just because it happens to be insensitive to end of file.  Instead, I was hoping that the VM would try harder to kill these processes as the Port is closed, or if the VM halts.&lt;br /&gt;
&lt;br /&gt;
In fact, letting a child process outlive the one that spawned it is unusual enough that the condition is called an &amp;quot;orphan process&amp;quot;.  The POSIX standard recommends that when this happens the process should be adopted by the top-level system process &amp;quot;init&amp;quot; if it exists, but this is a &amp;quot;should have&amp;quot; and not a must.  The reason it can be undesirable to allow this to happen at all is that the orphan process becomes entirely responsible for itself, potentially running forever without any more intervention according to the purpose of the process.  Even the system init process tracks its children, and can restart them in response to service commands.  Init will know nothing about its adopted, orphan processes.&lt;br /&gt;
&lt;br /&gt;
When I ran into this issue, I found the suggested workaround of writing a [https://hexdocs.pm/elixir/1.18.3/Port.html#module-zombie-operating-system-processes wrapper script] to track its child (the program originally intended to run), listen for the end of file from BEAM, and kill the external program.  How much simpler it would be if this workaround were already built into the Erlang Port module!&lt;br /&gt;
&lt;br /&gt;
It&#039;s always a pleasure to ask questions in the BEAM communities, they have earned a reputation as being friendly and open.  The first big tip was to look at the third-party library [https://hexdocs.pm/erlexec/ erlexec], which demonstrates some best practices that might be backported into the language itself.  Everyone speaking on the problem has generally agreed that the fragile clean up of external processes is a bug, and supported the idea that one of the &amp;quot;terminate&amp;quot; signals should be sent to spawned programs.&lt;br /&gt;
&lt;br /&gt;
Which signal to use is still an open issue, there&#039;s a softer version &amp;lt;code&amp;gt;HUP&amp;lt;/code&amp;gt; which says &amp;quot;Goodbye!&amp;quot; and the program is free to interpret as it will, the mid-level &amp;lt;code&amp;gt;TERM&amp;lt;/code&amp;gt; that I prefer because it makes the intention explicit but can still be blocked or handled gracefully if needed, and &amp;lt;code&amp;gt;KILL&amp;lt;/code&amp;gt; which is bursting with destructive potential.  The world of unix signals is a wild and scary place, on which there&#039;s a refreshing diversity of opinion around the Internet.&lt;br /&gt;
&lt;br /&gt;
==== Inside the BEAM ====&lt;br /&gt;
Despite its retro-futuristic appearance of being one of the most time-tested yet forward-facing programming environments, I was brought back to Earth by digging around inside the VM to find that it&#039;s just a C program like any other.  There&#039;s nothing holy about the BEAM emulator, there are some good and some great ideas about functional languages and they&#039;re buried in a mass of ancient procedural ifdefs, with unnerving memory management and typedefs wrapping the size of an integer on various platforms, just like you might find in other relics from the dark ages of computing, next to the Firefox or linux kernel source code.&lt;br /&gt;
&lt;br /&gt;
Tantalizingly, message-passing is at the core of the VM, but is not a first-class concept when reaching out to external processes.  There&#039;s some fancy footwork with [[W:Anonymous pipe|pipes]] and [[W:Dup (system call)|dup]], but communication is done with enums, unions, and bit-rattling stdlib.  I love it, but... it might something to look at on another rainy day.&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Draft:Elixir/bzip2-ex&amp;diff=179</id>
		<title>Draft:Elixir/bzip2-ex</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Draft:Elixir/bzip2-ex&amp;diff=179"/>
		<updated>2025-02-14T12:48:06Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Found another library, bzip2_decomp&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;An adventure story of my first Erlang/Elixir library binding (NIF).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Adam Wight, Sept 2022&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{Project|url=https://gitlab.com/adamwight/bzip2-ex}}&lt;br /&gt;
&lt;br /&gt;
== Problem statement ==&lt;br /&gt;
[[File:Phap Nang Ngam Nai Wannakhadi (1964, p 60).jpg|thumb|Phap Nang Ngam Nai Wannakhadi (1964, p 60).  [This painting is not titled, &amp;quot;Picking the low-hanging fruit&amp;quot;. -AW]]I wanted to process some large, compressed files containing Wikipedia content&amp;lt;ref&amp;gt;https://dumps.wikimedia.org/backup-index.html&amp;lt;/ref&amp;gt;, which couldn&#039;t be expanded in-place.  The typical approach to this problem is to stream the decompressed data through the desired analysis in memory and then throw it away.&lt;br /&gt;
&lt;br /&gt;
Decompression can be accomplished by piping through an external, command-line tool or by reading the file using a native Elixir codec.  In my case, I chose to mix these approaches by untarring through tar using a Port, but writing a native bzip2 library to perform the decompression, since none existed at the time.&lt;br /&gt;
&lt;br /&gt;
In hindsight, it would have been much simpler to use command-line bunzip2.  The native library should make it possible to use backpressure and concurrency.  But mostly I just got excited about a small gap in the BEAM ecosystem and wanted to teach myself how to write an Erlang native implemented function, or NIF&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erl_nif&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
How hard could it be to write a little binding...&lt;br /&gt;
&lt;br /&gt;
==Mysteries of libbzip2==&lt;br /&gt;
&lt;br /&gt;
The first interesting obstacle was that development of official bzip2 has stopped at the last stable release, with v1.0.x in 2019.&amp;lt;ref&amp;gt;The project page for bzip2 v1.0 is https://sourceware.org/bzip2/.&amp;lt;/ref&amp;gt;  A new group of people has been working towards a fork&amp;lt;ref&amp;gt;https://gitlab.com/bzip2/bzip2/&amp;lt;/ref&amp;gt; that they&#039;re calling version &amp;quot;1.1&amp;quot; but hopefully will avoid breaking changes to the programming interface.  This is still unreleased as of 2025.&lt;br /&gt;
&lt;br /&gt;
The second point worth mentioning is that the bzip2 file format has no formal specification.  This situation is pretty common and I can&#039;t complain, because there&#039;s a brilliant reverse-engineering&amp;lt;ref&amp;gt;https://github.com/dsnet/compress/blob/master/doc/bzip2-format.pdf&amp;lt;/ref&amp;gt; effort which included the details I needed.&lt;br /&gt;
&lt;br /&gt;
==High- or low-level integration?==&lt;br /&gt;
As I mentioned, my own project was already in hard mode due to the decision to write a NIF at all.  But there was a second choice, between a the libbzip2 high-level interface&amp;lt;ref&amp;gt;https://sourceware.org/bzip2/manual/manual.html#hl-interface&amp;lt;/ref&amp;gt; which does everything for you: open the file and return its contents decompressed, or the low-level interface&amp;lt;ref&amp;gt;https://sourceware.org/bzip2/manual/manual.html#low-level&amp;lt;/ref&amp;gt; which works with block or even sub-block chunks of data.&lt;br /&gt;
&lt;br /&gt;
Here I learned the most important requirement of a NIF binding: it does work within the BEAM memory and process space but it must return control to the Elixir scheduler within a very short time period, less than 100ms or so.  Low-level it is, then!&lt;br /&gt;
&lt;br /&gt;
If you want to look into yet another approach, Moosieus&amp;lt;ref&amp;gt;https://github.com/Moosieus/bzip2_decomp&amp;lt;/ref&amp;gt; has written an Elixir binding for pure Rust bzip2-rs&amp;lt;ref&amp;gt;https://github.com/paolobarbolini/bzip2-rs&amp;lt;/ref&amp;gt;.  This looks good for decompression, but executes in a single run rather than streaming.&lt;br /&gt;
&lt;br /&gt;
==Native implemented function (NIF)==&lt;br /&gt;
[[File:Potato_sprout,_January_23,_2006.jpg|right|267x267px]]TODO...&lt;br /&gt;
&lt;br /&gt;
== Parallel processing ==&lt;br /&gt;
TODO: Stream vs block, what can write multiple streams, what are the challenges of detecting blocks...&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Draft:Elixir/bzip2-ex&amp;diff=178</id>
		<title>Draft:Elixir/bzip2-ex</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Draft:Elixir/bzip2-ex&amp;diff=178"/>
		<updated>2025-02-14T08:26:22Z</updated>

		<summary type="html">&lt;p&gt;Adamw: rewrite&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A chronicle of my first Erlang/Elixir library binding (NIF).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Adam Wight, Sept 2022&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{Project|url=https://gitlab.com/adamwight/bzip2-ex}}&lt;br /&gt;
&lt;br /&gt;
== Problem statement ==&lt;br /&gt;
[[File:Phap Nang Ngam Nai Wannakhadi (1964, p 60).jpg|thumb|Phap Nang Ngam Nai Wannakhadi (1964, p 60).  [This painting is not titled, &amp;quot;Picking the low-hanging fruit&amp;quot;. -AW]]I wanted to process some large, compressed files containing Wikipedia content&amp;lt;ref&amp;gt;https://dumps.wikimedia.org/backup-index.html&amp;lt;/ref&amp;gt;, which couldn&#039;t be expanded in place.  The typical approach to this problem is to stream the decompressed data through the desired analysis in memory and then throw it away.&lt;br /&gt;
&lt;br /&gt;
Decompression can be accomplished by piping through an external, command-line tool or by reading the file using a native Elixir codec.  In my case, I chose to mix these approaches by untarring through tar using a Port, but use a native bzip2 library to perform the decompression.&lt;br /&gt;
&lt;br /&gt;
In hindsight, it would have been much simpler to use command-line bunzip2.  The native library should make it possible to use backpressure and concurrency.  But mostly I just got excited about a small gap in the BEAM ecosystem and wanted to teach myself how to write an Erlang native implemented function, or NIF&amp;lt;ref&amp;gt;https://www.erlang.org/doc/apps/erts/erl_nif&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
How hard could it be to write a little binding...&lt;br /&gt;
&lt;br /&gt;
==Mysteries of libbzip2==&lt;br /&gt;
&lt;br /&gt;
The first interesting obstacle was that development of official bzip2 has stopped at the last stable release, with v1.0.x in 2019.&amp;lt;ref&amp;gt;The project page for bzip2 v1.0 is https://sourceware.org/bzip2/.&amp;lt;/ref&amp;gt;  A new group of people has been working towards a fork&amp;lt;ref&amp;gt;https://gitlab.com/bzip2/bzip2/&amp;lt;/ref&amp;gt; that they&#039;re calling version &amp;quot;1.1&amp;quot; but hopefully will avoid breaking changes to the programming interface.  This is still unreleased as of 2025.&lt;br /&gt;
&lt;br /&gt;
The second point worth mentioning is that the bzip2 file format has no formal specification.  This situation is pretty common and I can&#039;t complain, because there&#039;s a brilliant reverse-engineering&amp;lt;ref&amp;gt;https://github.com/dsnet/compress/blob/master/doc/bzip2-format.pdf&amp;lt;/ref&amp;gt; effort which included the details I needed.&lt;br /&gt;
&lt;br /&gt;
==High- or low-level integration?==&lt;br /&gt;
As I mentioned, my own project was already in hard mode due to the decision to write a NIF at all.  But there was a second choice, between a the libbzip2 high-level interface&amp;lt;ref&amp;gt;https://sourceware.org/bzip2/manual/manual.html#hl-interface&amp;lt;/ref&amp;gt; which does everything for you: open the file and return its contents decompressed, or the low-level interface&amp;lt;ref&amp;gt;https://sourceware.org/bzip2/manual/manual.html#low-level&amp;lt;/ref&amp;gt; which works with block or even sub-block chunks of data.&lt;br /&gt;
&lt;br /&gt;
Here I learned the most important requirement of a NIF binding: it does work within the BEAM memory and process space but it must return control to the Elixir scheduler within a very short time period, less than 100ms or so.  Low-level it is, then!&lt;br /&gt;
&lt;br /&gt;
==Native implemented function (NIF)==&lt;br /&gt;
[[File:Potato_sprout,_January_23,_2006.jpg|right|267x267px]]TODO...&lt;br /&gt;
&lt;br /&gt;
== Parallel processing ==&lt;br /&gt;
TODO: Stream vs block, what can write multiple streams, what are the challenges of detecting blocks...&lt;br /&gt;
&lt;br /&gt;
==References==&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Main_Page&amp;diff=177</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Main_Page&amp;diff=177"/>
		<updated>2025-01-16T22:09:44Z</updated>

		<summary type="html">&lt;p&gt;Adamw: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:Luddnet}}&lt;br /&gt;
[[File:Dice MET sf48-101-211a.jpg|alt=Thousand-year-old bone game die from the Islamic city of Nishapur.|left|96x96px|]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 300%;&amp;quot;&amp;gt;[[Special:Random|Try a random page?]]&amp;lt;/div&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Main_Page&amp;diff=176</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Main_Page&amp;diff=176"/>
		<updated>2025-01-16T22:08:43Z</updated>

		<summary type="html">&lt;p&gt;Adamw: image, random page, try to suppress title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:}}&lt;br /&gt;
[[File:Dice MET sf48-101-211a.jpg|alt=Thousand-year-old bone game die from the Islamic city of Nishapur.|left|96x96px|]]&lt;br /&gt;
&amp;lt;div style=&amp;quot;font-size: 300%;&amp;quot;&amp;gt;[[Special:Random|Try a random page?]]&amp;lt;/div&amp;gt;&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Ludd&amp;diff=175</id>
		<title>Ludd</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Ludd&amp;diff=175"/>
		<updated>2025-01-16T21:47:20Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Link to Blood in the Machine&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Ludd&#039;&#039;&#039; can refer to:&lt;br /&gt;
* [[w:Ned Ludd|Ned Ludd]], fictitious general of the [[w:Luddite|Luddite]] movement&lt;br /&gt;
* [[w:Lludd Llaw Eraint|Lludd Llaw Eraint]], mythological hero in Welsh mythology who rids Britain of three &amp;quot;plagues&amp;quot;&lt;br /&gt;
* [[w:Nuada|Nuada]], figure in Irish mythology&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* &amp;lt;cite&amp;gt;Merchant, Brian (2023). &#039;&#039;Blood in the Machine&#039;&#039;. [https://www.hachettebookgroup.com/titles/brian-merchant/blood-in-the-machine/9780316487740/ Little, Brown].&amp;lt;/cite&amp;gt;&lt;br /&gt;
* [[w:Lud (disambiguation)|Lud]], something else entirely&lt;br /&gt;
* [[w:Lod|Lod]], a city in Israel (formerly Lydda)&lt;br /&gt;
&lt;br /&gt;
[[Category:Disambiguation pages]]&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Ludd&amp;diff=174</id>
		<title>Ludd</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Ludd&amp;diff=174"/>
		<updated>2025-01-16T19:06:02Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Strip prefix from interwiki links&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Ludd&#039;&#039;&#039; can refer to:&lt;br /&gt;
* [[w:Ned Ludd|Ned Ludd]], fictitious general of the [[w:Luddite|Luddite]] movement&lt;br /&gt;
* [[w:Lludd Llaw Eraint|Lludd Llaw Eraint]], mythological hero in Welsh mythology who rids Britain of three &amp;quot;plagues&amp;quot;&lt;br /&gt;
* [[w:Nuada|Nuada]], figure in Irish mythology&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[w:Lud (disambiguation)|Lud]]&lt;br /&gt;
* [[w:Lod|Lod]], a city in Israel (formerly Lydda)&lt;br /&gt;
&lt;br /&gt;
[[Category:Disambiguation pages]]&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
	<entry>
		<id>https://mw.ludd.net/w/index.php?title=Ludd&amp;diff=173</id>
		<title>Ludd</title>
		<link rel="alternate" type="text/html" href="https://mw.ludd.net/w/index.php?title=Ludd&amp;diff=173"/>
		<updated>2025-01-16T19:04:40Z</updated>

		<summary type="html">&lt;p&gt;Adamw: Disambiguation target for domain homepage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Ludd&#039;&#039;&#039; can refer to:&lt;br /&gt;
* [[w:Ned Ludd]], fictitious general of the [[w:Luddite]] movement&lt;br /&gt;
* [[w:Lludd Llaw Eraint]], mythological hero in Welsh mythology who rids Britain of three &amp;quot;plagues&amp;quot;&lt;br /&gt;
* [[w:Nuada]], figure in Irish mythology&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* [[Lud (disambiguation)]]&lt;br /&gt;
* [[Lod]], a city in Israel (formerly Lydda)&lt;br /&gt;
&lt;br /&gt;
[[Category:Disambiguation pages]]&lt;/div&gt;</summary>
		<author><name>Adamw</name></author>
	</entry>
</feed>