@@ -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 ) ::
0 commit comments