@@ -10,7 +10,7 @@ defmodule ExCmd.Process.Pipe do
1010 port: port | nil ,
1111 monitor_ref: reference ( ) | nil ,
1212 owner: pid | nil ,
13- status: :open | :closed
13+ status: :open | :closed | :eof
1414 }
1515
1616 defstruct [ :name , :port , :monitor_ref , :owner , status: :init ]
@@ -37,14 +37,19 @@ defmodule ExCmd.Process.Pipe do
3737 end
3838
3939 @ spec open? ( t ) :: boolean ( )
40- def open? ( pipe ) , do: pipe . status == :open
40+ def open? ( pipe ) , do: pipe . status in [ :open , :eof ]
4141
42- @ spec read ( t , non_neg_integer , pid ) :: { :error , :eagain } | { :error , term }
42+ @ spec read ( t , non_neg_integer , pid ) :: :eof | { :error , :eagain } | { :error , term }
4343 def read ( pipe , size , caller ) do
44- if caller != pipe . owner do
45- { :error , :pipe_closed_or_invalid_caller }
46- else
47- { :error , :eagain } = Proto . read ( pipe . port , size )
44+ cond do
45+ caller != pipe . owner ->
46+ { :error , :pipe_closed_or_invalid_caller }
47+
48+ pipe . status == :eof ->
49+ :eof
50+
51+ true ->
52+ { :error , :eagain } = Proto . read ( pipe . port , size )
4853 end
4954 end
5055
@@ -59,20 +64,30 @@ defmodule ExCmd.Process.Pipe do
5964
6065 @ spec close ( t , pid ) :: { :ok , t } | { :error , :pipe_closed_or_invalid_caller }
6166 def close ( % Pipe { } = pipe , caller ) do
62- if caller != pipe . owner do
63- { :error , :pipe_closed_or_invalid_caller }
64- else
65- Process . demonitor ( pipe . monitor_ref , [ :flush ] )
66- :ok = Proto . close ( pipe . port , pipe . name )
67- pipe = % Pipe { pipe | status: :closed , monitor_ref: nil , owner: nil }
67+ cond do
68+ caller != pipe . owner ->
69+ { :error , :pipe_closed_or_invalid_caller }
6870
69- { :ok , pipe }
71+ pipe . status in [ :closed , :eof ] ->
72+ # Already closed/eof - skip protocol command
73+ if pipe . monitor_ref do
74+ Process . demonitor ( pipe . monitor_ref , [ :flush ] )
75+ end
76+
77+ pipe = % Pipe { pipe | status: :closed , monitor_ref: nil , owner: nil }
78+ { :ok , pipe }
79+
80+ true ->
81+ Process . demonitor ( pipe . monitor_ref , [ :flush ] )
82+ :ok = Proto . close ( pipe . port , pipe . name )
83+ pipe = % Pipe { pipe | status: :closed , monitor_ref: nil , owner: nil }
84+ { :ok , pipe }
7085 end
7186 end
7287
7388 @ spec set_owner ( t , pid ) :: { :ok , t } | { :error , :closed }
7489 def set_owner ( % Pipe { } = pipe , new_owner ) do
75- if pipe . status == : open do
90+ if open? ( pipe ) do
7691 ref = Process . monitor ( new_owner )
7792 Process . demonitor ( pipe . monitor_ref , [ :flush ] )
7893 pipe = % Pipe { pipe | owner: new_owner , monitor_ref: ref }
@@ -82,4 +97,16 @@ defmodule ExCmd.Process.Pipe do
8297 { :error , :closed }
8398 end
8499 end
100+
101+ @ doc """
102+ Mark pipe as EOF when remote side closes.
103+
104+ Only used for stdout/stderr. `:eof` means remote closed,
105+ `:closed` means we explicitly closed.
106+ """
107+ @ spec mark_eof ( t ) :: { :ok , t }
108+ def mark_eof ( % Pipe { } = pipe ) do
109+ pipe = % Pipe { pipe | status: :eof }
110+ { :ok , pipe }
111+ end
85112end
0 commit comments