Skip to content

Commit a8f8632

Browse files
committed
Handle when there are no input and stream terminates early
1 parent 9e32ab4 commit a8f8632

File tree

2 files changed

+39
-7
lines changed

2 files changed

+39
-7
lines changed

lib/ex_cmd/stream.ex

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ defmodule ExCmd.Stream do
103103
:exit_timeout,
104104
:ignore_epipe,
105105
:stream_exit_status,
106-
process_status: :running
106+
process_status: :running,
107+
stream_reader_status: :started
107108
]
108109
end
109110

@@ -121,6 +122,7 @@ defmodule ExCmd.Stream do
121122
{[IO.iodata_to_binary(data)], state}
122123

123124
:eof ->
125+
state = %State{state | stream_reader_status: :eof}
124126
state = stop_process(state)
125127

126128
if state.stream_exit_status do
@@ -185,7 +187,7 @@ defmodule ExCmd.Stream do
185187
defp start_input_streamer(%Sink{process: process} = sink, input) do
186188
case input do
187189
:no_input ->
188-
Task.async(fn -> :ok end)
190+
Task.async(fn -> :no_input end)
189191

190192
{:enumerable, enum} ->
191193
stream_to_sink(process, fn -> Enum.into(enum, sink) end)
@@ -229,19 +231,49 @@ defmodule ExCmd.Stream do
229231
process_result = Process.await_exit(state.process, state.exit_timeout)
230232
writer_task_status = Task.await(state.writer_task)
231233

234+
if state.stream_reader_status != :eof do
235+
handle_early_stream_exit(process_result, writer_task_status)
236+
else
237+
handle_normal_exit(process_result, writer_task_status)
238+
end
239+
end
240+
241+
defp await_exit(%{process_status: status}), do: status
242+
243+
defp handle_early_stream_exit(process_result, writer_task_status) do
232244
case {process_result, writer_task_status} do
233-
{_process_exit_status, {:error, :epipe}} ->
245+
# if we don't have input and stream reader exits early then we don't care about
246+
# the exit status, since we are not reading the complete output of the co mmand,
247+
# we can't depend on the exit status.
248+
{{:ok, _status}, :no_input} ->
234249
{:error, :epipe}
235250

236-
{{:ok, status}, :ok} ->
251+
# Same as above, the command might be killed due to early stream exit
252+
{{:error, :killed}, :no_input} ->
253+
{:error, :epipe}
254+
255+
_rest ->
256+
handle_normal_exit(process_result, writer_task_status)
257+
end
258+
end
259+
260+
defp handle_normal_exit(process_result, writer_task_status) do
261+
case {process_result, writer_task_status} do
262+
# if we writer fails with EPIPE then exist status does not matter
263+
{{:ok, _status}, {:error, :epipe}} ->
264+
{:error, :epipe}
265+
266+
# we might be getting `:killed` exit status due to EPIPE
267+
{{:error, :killed}, {:error, :epipe}} ->
268+
{:error, :epipe}
269+
270+
{{:ok, status}, _writer_status} ->
237271
{:exit, {:status, status}}
238272

239273
{{:error, reason}, _writer_status} ->
240274
{:error, reason}
241275
end
242276
end
243-
244-
defp await_exit(%{process_status: status}), do: status
245277
end
246278

247279
@spec normalize_input(term) ::

test/ex_cmd_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defmodule ExCmdTest do
3737
end
3838

3939
test "stream!/2 early termination with non-zero exit raises" do
40-
assert_raise ExCmd.Stream.AbnormalExit, "program exited with exit status: 120", fn ->
40+
assert_raise ExCmd.Stream.AbnormalExit, "program exited due to :epipe error", fn ->
4141
ExCmd.stream!(["sh", "-c", "echo foo; exit 120"])
4242
|> Enum.take(1)
4343
end

0 commit comments

Comments
 (0)