Not sure if this is an Erlang or Elixir question. So I have an escript
written in Elixir that launches some diagnostic processes on a target VM. The escript
then enters a recursive function that accepts user input with IO.gets
, which blocks until someone enters something on the keyboard. Now the issue is that I want the escript
to accept messages from the diagnostic processes in the VM to do things like change prompt, update the display etc; but as I said it is blocked by IO.gets
, so the mailbox will slowly fill up with unread messages.
The solution I found is rather hacky and I’m sure there must be a better way:
##
## This is in the escript
##
defp do_start_work(state) do
## Some code goes here
## This is where the hack happens
message_proxy()
## Get input from stdio
wait_for_user_events(state)
end
defp wait_for_user_events(%{target_node: target_node} = state) do
prompt = make_prompt(state)
case IO.gets(prompt) |> String.trim() do
"start" ->
# Sends a message to the process on the target node/VM
Diagnostic.start_checks(target_node)
wait_for_user_events(state)
"reset" ->
Diagnostic.reset_checks(target_node)
wait_for_user_events(state)
......... snip .......
message ->
case handle_mailbox(message) do
# These are all Erlang/Elixir messages
:diagnostic_started ->
started_work()
wait_for_user_events(state)
{:state_update, new_state} ->
wait_for_user_events(new_state)
..... snip .....
_ ->
wait_for_user_events(state)
end
end
end
##
## Gets actual messages from its mailbox
##
defp handle_mailbox("message_waiting_in_mailbox") do
receive do
msg -> msg
after
2000 -> :ok
end
end
##
## The hack
##
def message_proxy() do
main_pid = self()
spawn(fn ->
# Other processes send to this registered name
Process.register(self(), :message_proxy_handler)
# Find the group leader port that IO.gets is blocked on
[port] = Process.info(Process.group_leader())[:links] |> Enum.filter(&is_port(&1))
message_proxy_loop(port, main_pid)
end)
end
defp message_proxy_loop(port, main_pid) do
receive do
message ->
# Got a message to be proxied, send it to the main process stuck on IO.gets
# in wait_for_user_events/1
send(main_pid, message)
# Send a unique string to the group_leader port that matches that in handle_mailbox/1
send(Process.group_leader(), {port, {:data, "message_waiting_in_mailbox\n"}})
message_proxy_loop(port, main_pid)
end
end
``