Skip to content

Commit 7ab1ae8

Browse files
committed
Avoid deadlocks in spawning commands
1 parent a7b604c commit 7ab1ae8

File tree

8 files changed

+71
-51
lines changed

8 files changed

+71
-51
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1616
### Removed
1717

1818
### Fixed
19+
- Replaced pipes with `Open3.capture3` to avoid deadlocks when commands have too much output
1920

2021
### Security
2122

lib/arduino_ci/arduino_cmd.rb

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,18 @@ def set_pref(key, value)
114114
end
115115

116116
# run the arduino command
117-
def _run(*args, **kwargs)
117+
# @return [bool] whether the command succeeded
118+
def _run_and_output(*args, **kwargs)
118119
raise "Ian needs to implement this in a subclass #{args} #{kwargs}"
119120
end
120121

121-
# build and run the arduino command
122-
def run(*args, **kwargs)
122+
# run the arduino command
123+
# @return [Hash] keys for :success, :out, and :err
124+
def _run_and_capture(*args, **kwargs)
125+
raise "Ian needs to implement this in a subclass #{args} #{kwargs}"
126+
end
127+
128+
def _wrap_run(work_fn, *args, **kwargs)
123129
# do some work to extract & merge environment variables if they exist
124130
has_env = !args.empty? && args[0].class == Hash
125131
env_vars = has_env ? args[0] : {}
@@ -129,26 +135,21 @@ def run(*args, **kwargs)
129135

130136
shell_vars = env_vars.map { |k, v| "#{k}=#{v}" }.join(" ")
131137
@last_msg = " $ #{shell_vars} #{full_args.join(' ')}"
132-
_run(*full_cmd, **kwargs)
138+
work_fn.call(*full_cmd, **kwargs)
139+
end
140+
141+
# build and run the arduino command
142+
def run_and_output(*args, **kwargs)
143+
_wrap_run((proc { |*a, **k| _run_and_output(*a, **k) }), *args, **kwargs)
133144
end
134145

135146
# run a command and capture its output
136147
# @return [Hash] {:out => String, :err => String, :success => bool}
137148
def run_and_capture(*args, **kwargs)
138-
pipe_out, pipe_out_wr = IO.pipe
139-
pipe_err, pipe_err_wr = IO.pipe
140-
our_kwargs = { out: pipe_out_wr, err: pipe_err_wr }
141-
eventual_kwargs = our_kwargs.merge(kwargs)
142-
success = run(*args, **eventual_kwargs)
143-
pipe_out_wr.close
144-
pipe_err_wr.close
145-
str_out = pipe_out.read
146-
str_err = pipe_err.read
147-
pipe_out.close
148-
pipe_err.close
149-
@last_err = str_err
150-
@last_out = str_out
151-
{ out: str_out, err: str_err, success: success }
149+
ret = _wrap_run((proc { |*a, **k| _run_and_capture(*a, **k) }), *args, **kwargs)
150+
@last_err = ret[:err]
151+
@last_out = ret[:out]
152+
ret
152153
end
153154

154155
# run a command and don't capture its output, but use the same signature

lib/arduino_ci/arduino_cmd_linux.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,15 @@ def _lib_dir
3939
end
4040

4141
# run the arduino command
42-
def _run(*args, **kwargs)
43-
@display_mgr.run(*args, **kwargs)
42+
# @return [bool] whether the command succeeded
43+
def _run_and_output(*args, **kwargs)
44+
@display_mgr.run_and_output(*args, **kwargs)
45+
end
46+
47+
# run the arduino command
48+
# @return [Hash] keys for :success, :out, and :err
49+
def _run_and_capture(*args, **kwargs)
50+
@display_mgr.run_and_capture(*args, **kwargs)
4451
end
4552

4653
def run_with_gui_guess(message, *args, **kwargs)

lib/arduino_ci/arduino_cmd_linux_builder.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@ def _lib_dir
2121
end
2222

2323
# run the arduino command
24-
# @param [Array<String>] Arguments for the run command
25-
# @return [bool] Whether the command succeeded
26-
def _run(*args, **kwargs)
27-
Host.run(*args, **kwargs)
24+
# @return [bool] whether the command succeeded
25+
def _run_and_output(*args, **kwargs)
26+
Host.run_and_output(*args, **kwargs)
27+
end
28+
29+
# run the arduino command
30+
# @return [Hash] keys for :success, :out, and :err
31+
def _run_and_capture(*args, **kwargs)
32+
Host.run_and_capture(*args, **kwargs)
2833
end
2934

3035
end

lib/arduino_ci/arduino_cmd_osx.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,15 @@ class ArduinoCmdOSX < ArduinoCmd
1414
flag :verify, "--verify"
1515

1616
# run the arduino command
17-
def _run(*args, **kwargs)
18-
Host.run(*args, **kwargs)
17+
# @return [bool] whether the command succeeded
18+
def _run_and_output(*args, **kwargs)
19+
Host.run_and_output(*args, **kwargs)
20+
end
21+
22+
# run the arduino command
23+
# @return [Hash] keys for :success, :out, and :err
24+
def _run_and_capture(*args, **kwargs)
25+
Host.run_and_capture(*args, **kwargs)
1926
end
2027

2128
def _lib_dir

lib/arduino_ci/cpp_library.rb

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,13 @@ def header_dirs
108108

109109
# wrapper for the GCC command
110110
def run_gcc(*args, **kwargs)
111-
pipe_out, pipe_out_wr = IO.pipe
112-
pipe_err, pipe_err_wr = IO.pipe
113111
full_args = ["g++"] + args
114112
@last_cmd = " $ #{full_args.join(' ')}"
115-
our_kwargs = { out: pipe_out_wr, err: pipe_err_wr }
116-
eventual_kwargs = our_kwargs.merge(kwargs)
117-
success = Host.run(*full_args, **eventual_kwargs)
118113

119-
pipe_out_wr.close
120-
pipe_err_wr.close
121-
str_out = pipe_out.read
122-
str_err = pipe_err.read
123-
pipe_out.close
124-
pipe_err.close
125-
@last_err = str_err
126-
@last_out = str_out
127-
success
114+
ret = Host.run_and_capture(*full_args, **kwargs)
115+
@last_err = ret[:err]
116+
@last_out = ret[:out]
117+
ret[:success]
128118
end
129119

130120
# Return the GCC version
@@ -214,7 +204,7 @@ def run_test_file(executable)
214204
@last_cmd = executable
215205
@last_out = ""
216206
@last_err = ""
217-
Host.run(executable)
207+
Host.run_and_output(executable)
218208
end
219209

220210
end

lib/arduino_ci/display_manager.rb

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,26 +150,30 @@ def with_display
150150
end
151151
end
152152

153-
# run a command in a display
154-
# @return [bool]
155-
def run(*args, **kwargs)
156-
ret = false
153+
def wrap_run(work_fn, *args, **kwargs)
154+
ret = nil
157155
# do some work to extract & merge environment variables if they exist
158156
has_env = !args.empty? && args[0].class == Hash
159157
with_display do |env_vars|
160158
env_vars = {} if env_vars.nil?
161159
env_vars.merge!(args[0]) if has_env
162160
actual_args = has_env ? args[1..-1] : args # need to shift over if we extracted args
163161
full_cmd = env_vars.empty? ? actual_args : [env_vars] + actual_args
164-
ret = Host.run(*full_cmd, **kwargs)
162+
ret = work_fn.call(*full_cmd, **kwargs)
165163
end
166164
ret
167165
end
168166

169-
# run a command in a display with no output
167+
# run a command in a display, outputting to stdout
168+
# @return [bool]
169+
def run_and_output(*args, **kwargs)
170+
wrap_run((proc { |*a, **k| Host.run_and_output(*a, **k) }), *args, **kwargs)
171+
end
172+
173+
# run a command in a display, capturing output
170174
# @return [bool]
171-
def run_silent(*args)
172-
run(*args, out: File::NULL, err: File::NULL)
175+
def run_and_capture(*args, **kwargs)
176+
wrap_run((proc { |*a, **k| Host.run_and_capture(*a, **k) }), *args, **kwargs)
173177
end
174178

175179
# @return [Hash] the environment variables for the display

lib/arduino_ci/host.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'os'
2+
require 'open3'
23

34
module ArduinoCI
45

@@ -20,8 +21,12 @@ def self.which(cmd)
2021
nil
2122
end
2223

23-
# run a command in a display
24-
def self.run(*args, **kwargs)
24+
def self.run_and_capture(*args, **kwargs)
25+
stdout, stderr, status = Open3.capture3(*args, **kwargs)
26+
{ out: stdout, err: stderr, success: status.exitstatus.zero? }
27+
end
28+
29+
def self.run_and_output(*args, **kwargs)
2530
system(*args, **kwargs)
2631
end
2732

0 commit comments

Comments
 (0)