ConnorRigby
I have a GenServer that reads/writes to a Linux Pipe or Fifo. I need to do:
{:ok, fifo} = :file.open('/path/to/fifo', [:read, :write, :binary])
{:ok, <<protocol_pattern_match>>} = :file.read(fifo, protocol_size)
Except :file.read/2 blocks the entire calling process until it is complete similar to read() in C. This is fine because i should be able to just do task = Task.async(:file, :read, [protocol_size]) and get the result in
handle_info/2. Maybe i misunderstood the docs, but that doesn’t seem to be working for me. I’m expecting to get handle_info({ref, {:ok, <<protocol_pattern_match>>}, %{task: %{ref: ref}}) but that doesn’t seem to happen.
My other issue is that Task.shutdown/2 or :file.close/1 do not seem to be working as expected.
the docs for Task.shutdown/2 say when a calling process exits, the task should exit, but even using
Task.shutdown(state.task, :brutal_kill) doesn’t allow me to call :file.close(state.fifo) in terminate/2. (It just blocks forever)
anyway here’s the entire GenServer implementation:
defmodule PipeWorker do
@moduledoc """
Proxy for IO operations.
"""
use GenServer
require Logger
def start_link(pipe_name) do
GenServer.start_link(__MODULE__, pipe_name)
end
def close(pipe) do
GenServer.stop(pipe, :normal)
end
def read(pipe, size) do
GenServer.call(pipe, {:read, [size]}, :infinity)
end
def write(pipe, packet) do
GenServer.call(pipe, {:write, [packet]}, :infinity)
end
def init(pipe_name) do
with {_, 0} <- System.cmd("mkfifo", [pipe_name]),
{:ok, pipe} <- :file.open(to_charlist(pipe_name), [:read, :write, :binary]) do
{:ok, %{pipe_name: pipe_name, pipe: pipe, task: nil, caller: nil}}
else
{:error, _} = error -> {:stop, error}
{_, _num} -> {:stop, {:error, "mkfifo"}}
end
end
def terminate(_, state) do
Logger.warn("PipeWorker #{state.pipe_name} exit")
state.task && Task.shutdown(state.task, :brutal_kill)
Logger.warn("Pipe Task shut down")
IO.inspect(state.pipe, label: "pipe")
# :file.close(state.pipe) # blocks indefinitely no matter what. Shell becomes unresponsive.
# Logger.warn("Pipe closed")
File.rm!(state.pipe_name)
Logger.warn("Pipe removed")
end
def handle_call({cmd, args}, {pid, _} = _from, state) do
IO.inspect([state.pipe | args], label: "Pipe task args")
task = Task.async(:file, cmd, [state.pipe | args])
IO.inspect(task, label: "Pipe task")
{:reply, task.ref, %{state | task: task, caller: pid}}
end
# This is never called?
def handle_info({ref, result}, %{task: %{ref: ref}, caller: pid} = state) do
IO.inspect({ref, result}, label: "Task result")
send(pid, {__MODULE__, ref, result})
{:noreply, %{state | task: nil, caller: nil}}
end
end
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #ai
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #elixirconf-eu
- #metaprogramming
- #hex










Showing Posts 17 to 8- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
ConnorRigby
Right. I chose the port method and it seems to be working well!
rvirding
Well the
file:readoperation blocks by design. Two solutions have already been mentioned: put the read in a separate process which sends you a message when done and which you can kill when you give up on it; use ports which are non-blocking.ConnorRigby
Yes, but i understand what you are saying. I understand that
file:read(Fifo, Size)will block untilSizeamount of bytes are available. This is fine, but in my case i didn’t want to block thegen_serverprocess while waiting on those bytes to arrive. In my case they may never arrive for the life of this fifo, meaning i need to be able to cancel, or stop the existingreadoperation.rvirding
I have a stupid question. You are reading with:
Do you write enough bytes into the fifo so this read can complete? I expect yes but it is always best to ask. Start with the simple questions first.
ConnorRigby
For completeness here is my (mostly) final implementation:
and usage is something like:
For EVEN MORE completeness here’s the NIF implementation i won’t be using: https://github.com/ConnorRigby/pipe_fitter/blob/master/c_src/erl_pipe_nif.c
where usage is something like:
Anyway Thanks again @josevalim for making me check out the
Portoption again. Hope the information here is enough to answer anyone elses questions in the future.ConnorRigby
I’m still investigating solutions. I whipped up a quick NIF that works similarly to
:file.open('test.pipe', [:read, :write, :raw]but allows for callingread/2within aTask. Unfortunately this is the best solution so far, however i don’t like putting NIFs into production unless there is no other options.josevalim
Good point. Can that be done at the pipe level? I.e. have some UNIX utility that buffers it for you or similar?
ConnorRigby
I can confirm that this does in fact allow reading and writing from a named pipe. However it does add the requirement of buffering now, since you are not telling the port how much data you want to read. Either way thanks for the help!
ConnorRigby
ah ha! i was trying to use
spawnwhich was not needed. Will try this method out and report back.josevalim
So this worked:
After typing the receive, I ran
echo "foo" > test.pipein another terminal. Could it be something related to permissions as the error message says?