Skip to content

Commit

Permalink
Do not leak stderr FDs
Browse files Browse the repository at this point in the history
  • Loading branch information
akash-akya committed Dec 15, 2023
1 parent 0d9c1a2 commit 8d9b443
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 4 deletions.
12 changes: 8 additions & 4 deletions lib/exile/process/exec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,16 @@ defmodule Exile.Process.Exec do
# FDs are managed by the NIF resource life-cycle
{:ok, stdout} = Nif.nif_create_fd(stdout_fd)
{:ok, stdin} = Nif.nif_create_fd(stdin_fd)
{:ok, stderr} = Nif.nif_create_fd(stderr_fd)

{:ok, stderr} =
if stderr == :consume do
Nif.nif_create_fd(stderr_fd)
stderr =
if stderr_mode == :consume do
stderr
else
{:ok, nil}
# we have to explicitly close FD passed over socket.
# Since it will be tracked by the OS and kept open until we close.
Nif.nif_close(stderr)
nil
end

{stdin, stdout, stderr}
Expand Down
27 changes: 27 additions & 0 deletions test/exile/sync_process_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ defmodule Exile.SyncProcessTest do
assert %{active: 0, workers: 0} = DynamicSupervisor.count_children(Exile.WatcherSupervisor)
end

test "FDs are not leaked" do
before_count = opened_pipes()

for _ <- 1..100 do
{:ok, s} = Process.start_link(~w(date))
:ok = Process.close_stdin(s)
assert {:ok, _} = Process.read_any(s, 100)
assert :eof = Process.read_any(s, 100)
assert {:ok, 0} = Process.await_exit(s, 100)
end

# let the dust settle
:timer.sleep(2000)

after_count = opened_pipes()

assert before_count == after_count
end

defp opened_pipes do
{pipe_count, 0} = System.shell(~s(lsof -a -p #{:os.getpid()} | grep " PIPE " | wc -l))

pipe_count
|> String.trim()
|> String.to_integer()
end

defp stop_all_children(sup) do
DynamicSupervisor.which_children(sup)
|> Enum.each(fn {_, pid, _, _} ->
Expand Down

0 comments on commit 8d9b443

Please sign in to comment.