PragTob

PragTob

Starting & shutting down iex with a Port - gracefully

Hello my fellow elixir enthusiasts!

I want to start iex in a port then shut it down and I want to do so gracefully. Here is my attempt with some things outcommented that I tried:

port = Port.open({:spawn, "iex"}, [:binary])

# wait for startup
receive do
  {^port, {:data, "iex(1)> "}} -> :ok
end

# attemtps at closing iex in the port
# send(port, {:command, "^C"})
# send(port, {:command, "^C"})
# send(port, {:command, "^\\"})
# port_info = port |> Port.info() |> IO.inspect()
# os_pid = Access.get(port_info, :os_pid)
# System.cmd("kill", [to_string(os_pid)])

# attempts at closing just the port
Port.close(port)
# send(port, {self(), :close})
# receive do
#   {^port, :closed} -> :ok
# end
IO.puts("script finished")

Running this leads to the following output:

tobi@qiqi:~/github/elixir_playground(main)$ mix run scripts/close_iex.exs; echo $?
script finished
Failed to write log message to stdout, trying stderr

11:22:42.395 [error] Process #PID<0.101.0> raised an exception
** (FunctionClauseError) no function clause matching in :string.trim_l/2
    (stdlib 5.1.1) string.erl:884: :string.trim_l(:eof, [~c"\r\n", 9, 10, 11, 12, 13, 32, 133, 8206, 8207, 8232, 8233])
    (stdlib 5.1.1) string.erl:303: :string.trim/3
    (kernel 9.1) group.erl:534: :group.get_chars_apply/10
    (kernel 9.1) group.erl:199: :group.io_request/6
    (kernel 9.1) group.erl:125: :group.server_loop/3
Failed to write log message to stdout, trying stderr

11:22:42.406 [info] Failed to write to standard out (:epipe)
0

(you can also check it out at https://github.com/PragTob/elixir_playground/blob/main/scripts/close_iex.exs)

I can not get it to finish without any errors like these. To be clear, the command exits with 0 aka job well done.

I’m running 1.16.0-rc.1 x 26.1.2 on Linux but I’m pretty sure this happens on everything.

As for the inevitable question “why would anyone want to do this?” the script is based off a test in benchee: https://github.com/bencheeorg/benchee/blob/main/test/benchee_test.exs#L1032-L1050

It’s a test that makes sure Benchee warns when run inside iex. The test works. It passes. However it spouts these error messages and seems to (partially) break the shell as I went down to diagnose that if the tests are initiated via Mix.Shell.IO.cmd that then infinitely hangs which breaks ex_guard which makes me sad (cc: @slashmili / Stopped working for benchee / only runs one command · Issue #68 · slashmili/ex_guard · GitHub ).

I’m confident though that if we solve this script, we solve that test, i.e. I’m confident it’s connected to the error Failed to write log message to stdout, trying stderr

Thanks everyone for taking a look and helping! :green_heart:

Marked As Solved

garazdawi

garazdawi

Erlang Core Team

It should be possible to do Port.close/1 without seeing that crash. The crash is caused by a disagreement by group and IEx.Server.__parse__/2. group expects __parse__/2 to return {stop, eof, eof}, while it returns {stop, eof, []}. I think that in this case it is group that is wrong and it should be able to handle what __parse__/2 is doing. I’ll write a fix for this.

As a workaround you can close the shell by sending "\aq\n". i.e.

port = Port.open({:spawn, "iex"}, [:binary, :exit_status])
receive do
  {^port, {:data, "iex(1)> "}} -> :ok
end
send(port, {self, {:command, "\a"}})
send(port, {self, {:command, "q\n"}})
receive do
  {^port, {:exit_status, num}} -> num
end

Most likely this error does not exist when using Erlang/OTP < 26.

Also Liked

garazdawi

garazdawi

Erlang Core Team
PragTob

PragTob

Wow, thanks a ton! Sorry for the assumption old erlang versions also had the problem - you’re right, they don’t!

@garazdawi it’s just amazing that I can ask a question here, someone from the Erlang Core team takes the time to look at it and answer it and figures out it’s actually a bug and implements a fix. All within ~3h. Absolutely amazing, tack så mycket!

The script needs some small adustments (self() and the last receive does not work/happen for me):

port = Port.open({:spawn, "iex"}, [:binary])

# wait for startup
receive do
  {^port, {:data, "iex(1)> "}} -> :ok
end

send(port, {self(), {:command, "\a"}})
send(port, {self(), {:command, "q\n"}})

@garazdawi any reference where I can learn about the commands/inputs we’re sending there? quick google wasn’t successful but probably that’s me.

Where Next?

Popular in Questions Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
sergio
I couldn’t find any guides that worked well with Phoenix 1.6.0 and esbuild. I hope this helps people test the waters and eases you into t...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

We're in Beta

About us Mission Statement