Elixir/Ports and external process wiring: Difference between revisions
light edits |
→Asynchronous call and communication: link to doc heading |
||
| (4 intermediate revisions by the same user not shown) | |||
| Line 59: | Line 59: | ||
; <code>--itemize-changes</code> : list the operations taken on each file | ; <code>--itemize-changes</code> : list the operations taken on each file | ||
; <code>--out-format=FORMAT</code> : any custom format string following rsyncd.conf's <code>log format</code><ref>https://man. | ; <code>--out-format=FORMAT</code> : any custom format string following rsyncd.conf's <code>log format</code><ref>[https://man.archlinux.org/man/rsyncd.conf.5#log~2 rsyncd.conf log format] docs</ref> | ||
}} | }} | ||
| Line 110: | Line 110: | ||
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. | 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. | ||
It's possible to send a signal by shelling out to unix <code>kill PID</code>, but BEAM | It's possible to find the operating system PID of the child process with <code>Port.info(port, :os_pid)</code> and send it a signal by shelling out to unix <code>kill PID</code>, but BEAM doesn'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'll keep looking for another way to "link" the processes. | ||
To debug what happens during <code>port_close</code> and to eliminate variables, I tried spawning <code>sleep 60</code> instead of rsync and I found that it behaves in exactly the same way: hanging until <code>sleep</code> 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: "sleep" is daemon-like so similar to rsync, but its behavior is much simpler to reason about. | To debug what happens during <code>port_close</code> and to eliminate variables, I tried spawning <code>sleep 60</code> instead of rsync and I found that it behaves in exactly the same way: hanging until <code>sleep</code> 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: "sleep" is daemon-like so similar to rsync, but its behavior is much simpler to reason about. | ||
| Line 123: | Line 123: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
If the program does blocking I/O, then a zero-byte <code>read</code> indicates the end of file condition. A program which does asynchronous I/O with <code>O_NONBLOCK</code><ref>[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]</ref> might instead detect EOF by listening for the <code>HUP</code> hang-up signal which is | If the program does blocking I/O, then a zero-byte <code>read</code> indicates the end of file condition. A program which does asynchronous I/O with <code>O_NONBLOCK</code><ref>[https://man.archlinux.org/man/open.2.en#O_NONBLOCK O_NONBLOCK docs]</ref> might instead detect EOF by listening for the <code>HUP</code> hang-up signal which is can be arranged (TODO: document how this can be done with <code>prctl</code>, and on which platforms). | ||
But here we'll focus on how processes can more generally affect each other through pipes. Surprising answer: without much effect! You can experiment with the <code>/dev/null</code> device which behaves like a closed pipe, for example compare these two commands: | But here we'll focus on how processes can more generally affect each other through pipes. Surprising answer: without much effect! You can experiment with the <code>/dev/null</code> device which behaves like a closed pipe, for example compare these two commands: | ||
| Line 168: | Line 168: | ||
Which signal to use is still an open question: | Which signal to use is still an open question: | ||
; <code>HUP</code> : sent to a process when its standard input stream is closed<ref>[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard "General Terminal Interface: Modem Disconnect"</ref> | ; <code>HUP</code> : sent to a process when its standard input stream is closed<ref>[https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap11.html#tag_11_01_10 POSIX standard "General Terminal Interface: Modem Disconnect"]</ref> | ||
; <code>TERM</code> : has a clear intention of "kill this thing" but still possible to trap at the target and handle in a customized way | ; <code>TERM</code> : has a clear intention of "kill this thing" but still possible to trap at the target and handle in a customized way | ||
| Line 176: | Line 176: | ||
There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port. | There is a refreshing diversity of opinion, so it could be worthwhile to make the signal configurable for each port. | ||
}} | }} | ||
== TODO: consistency with unix process groups == | |||
... there is something fun here about how unix already has process tree behaviors which are close analogues to a BEAM supervisor tree. | |||
== Future directions == | == Future directions == | ||