Processes linked to IEx are not killed when IEx exits

Hello,

So I have learnt a new thing recently, processes spawned in an IEx shell are not terminated after IEx exists, not even if they were started using link. The same obviously is not true for any two processes linked between them, and not even true for Erlang shells, so it is really about IEx only.

When inspecting the processes I can see that the link just disappears and the “child” process is still alive after IEx process (which was the “parent”) is long gone.

I have been researching this, but could not find any Elixir, Erlang or C code pointing in this direction.

Does anyone have any idea how and where this is implemented ?

2 Likes

:waving_hand: Welcome

That hasn’t been my experience and i feel like that shouldn’t be true.

If you could, give us a code example to see what’s going on.

1 Like

Found the explanation while discussing with Claude LLM, and the answer is as simple as possible: when shutting down IEx with “Ctrl + C” and “a” the process exits normally, so the link is not triggered, because it is not a crash.

2 Likes

This was interesting, so I wanted to try to reproduce it, and it’s true. Please excuse the old versions.

jstimpson@myhost:~$ ./bin/myapp remote
Erlang/OTP 25 [erts-13.2.2.15] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.15.8) - press Ctrl+C to exit (type h() ENTER for help)
iex(myapp@myhost)1> spawn_link(fn -> :timer.sleep(100000000) end)
#PID<0.5573.0>
iex(myapp@myhost)2> :erlang.is_process_alive(pid(0, 5573, 0))
true
iex(myapp@myhost)3>
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
       (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
a
jstimpson@myhost:~$ ./bin/myapp remote
Erlang/OTP 25 [erts-13.2.2.15] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.15.8) - press Ctrl+C to exit (type h() ENTER for help)
iex(myapp@myhost)1> :erlang.is_process_alive(pid(0, 5573, 0))
true
4 Likes

Ah - i was missing the part where the iex session was a remote connection (the VM is still alive) - if im understanding correctly.


What I see :smiley:

➜  ~ iex --sname main                    

Erlang/OTP 27 [erts-15.2.4] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.18.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(main@hades)1> pid = spawn_link(fn -> :timer.sleep(100000000) end)
#PID<0.112.0>
iex(main@hades)2> Process.alive?(pid)
true
iex(main@hades)3> 
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
       (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
a
➜  ~ iex --sname main 

Erlang/OTP 27 [erts-15.2.4] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.18.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(main@hades)1> Process.alive?(pid(0,112,0))
false
1 Like

That would truly be surprising if that one stayed alive. :smiley:

2 Likes

Do you mean iex or connecting to a release like @jstimps did?
In the latter case it is normal. In the former case once iex stops that beam instance also stops, so it would be very surprising if something survives.

1 Like

The behavior in the remote shell case is surprising to me personally, since my background is mostly in Erlang, in which it’s different. To demonstrate:

Terminal A:

~ ❯❯❯ iex --sname server # or `erl -sname server`
Erlang/OTP 27 [erts-15.2.6] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

Interactive Elixir (1.18.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(server@Mac)1>

Terminal B:

~ ❯❯❯ erl -remsh server
Erlang/OTP 27 [erts-15.2.6] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

Eshell V15.2.6 (press Ctrl+G to abort, type help(). for help)
(server@Mac)1> spawn_link(fun() -> timer:sleep(1000000000) end).
<0.127.0>
(server@Mac)2>
BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo
       (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution
a
~ ❯❯❯ erl -remsh server
Erlang/OTP 27 [erts-15.2.6] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit]

Eshell V15.2.6 (press Ctrl+G to abort, type help(). for help)
(server@Mac)1> is_process_alive(pid(0,127,0)).
false
(server@Mac)2>

Coming from Erlang, I had a natural expectation that halting an iex remote shell would trigger links on processes that were started in that shell. My expectation turned out to be wrong, but I can see how this would end up causing unexpected system behavior during debugging sessions. I will attempt a PR to the iex docs to explain the behavior, as I don’t see anything in there currently (although I might have missed it!)

Edit: doc change merged to elixir main :heart:

3 Likes

Ah - i was missing the part where the iex session was a remote connection (the VM is still alive) - if im understanding correctly.

Yeah, sorry, now I see that I forgot to mention it is all about remote shells, but even then I would say the IEx remote shell behaviour is not intuitive, although a friendly approach. We have found this approach because we were thinking how to start an ad-hoc memory cleanup process in production without it dying when we exit the remote shell, it was surprisingly easy :slight_smile:

I have checked both codes, and the Erlang shell code explicitly kills the evaluator, so I guess it was more explicitly implemented to avoid hanging processes. Personally I welcome both approaches, maybe shells should support both approaches with clarity on which mode is currently active for the shell users.

1 Like