From 8d9b443c3df10402a092bdc40d8f788ecbdfed94 Mon Sep 17 00:00:00 2001 From: akash-akya Date: Fri, 15 Dec 2023 23:51:52 +0530 Subject: [PATCH] Do not leak stderr FDs --- lib/exile/process/exec.ex | 12 ++++++++---- test/exile/sync_process_test.exs | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/exile/process/exec.ex b/lib/exile/process/exec.ex index a844d22..42ec470 100644 --- a/lib/exile/process/exec.ex +++ b/lib/exile/process/exec.ex @@ -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} diff --git a/test/exile/sync_process_test.exs b/test/exile/sync_process_test.exs index 164a69c..de7d441 100644 --- a/test/exile/sync_process_test.exs +++ b/test/exile/sync_process_test.exs @@ -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, _, _} ->