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 elixir_playground/scripts/close_iex.exs at main · PragTob/elixir_playground · GitHub)

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: benchee/test/benchee_test.exs at main · bencheeorg/benchee · GitHub

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

https://github.com/erlang/otp/pull/7966

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

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
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
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New

We're in Beta

About us Mission Statement