process_executer 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +3 -0
- data/lib/process_executer/errors.rb +11 -1
- data/lib/process_executer/monitored_pipe.rb +59 -13
- data/lib/process_executer/runner.rb +2 -6
- data/lib/process_executer/version.rb +1 -1
- data/lib/process_executer.rb +6 -5
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3af694308b0e9c5119b2ff63d11b6b351a25cc9f7d66ac2dd482c8525736595a
|
4
|
+
data.tar.gz: a669d2c3cceadeb7544be9765793eff22addc8c7bde10e13e4d36de447b26cb5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 670dcd425f0879def69e0875a90edae0f79280b44d4ea73d117b3875ad3eeb1cd6329f4b2d8a6a8c0d601f7b79eb72989288255d57067714f2e830f4dde3ddb2
|
7
|
+
data.tar.gz: 6a18db9777ecb26ebaa7b30c3fbadb81ae8adba1dcd60c92bf6d30510f30275336ef34c4b1c05c5df6356bcd62cd5b783736adb25462dd55d9d87deec6015945
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,25 @@ All notable changes to the process_executer gem will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## v3.2.0 (2025-04-04)
|
9
|
+
|
10
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v3.1.0..v3.2.0)
|
11
|
+
|
12
|
+
Changes since v3.1.0:
|
13
|
+
|
14
|
+
* 272d246 test: fix flaky test that fails on windows
|
15
|
+
* 1e121d8 test: add test for raising a SpawnError when Process.spawn raises an error
|
16
|
+
* 2a2aaac refactor: improve synchronization of the monitored pipe state
|
17
|
+
|
18
|
+
## v3.1.0 (2025-04-01)
|
19
|
+
|
20
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v3.0.0..v3.1.0)
|
21
|
+
|
22
|
+
Changes since v3.0.0:
|
23
|
+
|
24
|
+
* acb6385 fix: give Windows enough time to release its file lock so tmpdir can be deleted
|
25
|
+
* 3fe114a feat: wrap errors raised by `Process.spawn` in a `ProcessExecuter::SpawnError`
|
26
|
+
|
8
27
|
## v3.0.0 (2025-03-18)
|
9
28
|
|
10
29
|
[Full Changelog](https://github.com/main-branch/process_executer/compare/v2.0.0..v3.0.0)
|
data/README.md
CHANGED
@@ -179,6 +179,9 @@ following features:
|
|
179
179
|
* It raises an error if there is any problem with the subprocess. This behavior can
|
180
180
|
be turned off with the `raise_errors: false` option.
|
181
181
|
|
182
|
+
⚠️ `ProcessIOError` and `SpawnError` errors are not suppressed by giving the
|
183
|
+
`raise_errors: false` option.
|
184
|
+
|
182
185
|
```ruby
|
183
186
|
result = ProcessExecuter.run('echo "Hello World"', out: StringIO.new)
|
184
187
|
result.stdout #=> "Hello World\n"
|
@@ -17,7 +17,8 @@ module ProcessExecuter
|
|
17
17
|
# │ ├─> FailedError
|
18
18
|
# │ └─> SignaledError
|
19
19
|
# │ └─> TimeoutError
|
20
|
-
#
|
20
|
+
# ├─> ProcessIOError
|
21
|
+
# └─> SpawnError
|
21
22
|
# ```
|
22
23
|
#
|
23
24
|
# | Error Class | Description |
|
@@ -28,6 +29,7 @@ module ProcessExecuter
|
|
28
29
|
# | `SignaledError` | Raised when the command is terminated as a result of receiving a signal. This could happen if the process is forcibly terminated or if there is a serious system error. |
|
29
30
|
# | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the command times out and is killed via the SIGKILL signal. Raised when the operation takes longer than the specified timeout duration (if provided). |
|
30
31
|
# | `ProcessIOError` | Raised when an error was encountered reading or writing to the command's subprocess. |
|
32
|
+
# | `SpawnError` | Raised when the process could not execute. Check the |
|
31
33
|
#
|
32
34
|
# @example Rescuing any error
|
33
35
|
# begin
|
@@ -129,6 +131,14 @@ module ProcessExecuter
|
|
129
131
|
# @api public
|
130
132
|
#
|
131
133
|
class ProcessIOError < ProcessExecuter::Error; end
|
134
|
+
|
135
|
+
# Raised when spawn could not execute the process
|
136
|
+
#
|
137
|
+
# See the `cause` for the exception that Process.spawn raised.
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
#
|
141
|
+
class SpawnError < ProcessExecuter::Error; end
|
132
142
|
end
|
133
143
|
|
134
144
|
# rubocop:enable Layout/LineLength
|
@@ -58,14 +58,12 @@ module ProcessExecuter
|
|
58
58
|
|
59
59
|
assert_destination_is_compatible_with_monitored_pipe
|
60
60
|
|
61
|
+
@mutex = Mutex.new
|
62
|
+
@condition_variable = ConditionVariable.new
|
61
63
|
@chunk_size = chunk_size
|
62
64
|
@pipe_reader, @pipe_writer = IO.pipe
|
63
65
|
@state = :open
|
64
|
-
@thread =
|
65
|
-
Thread.current.report_on_exception = false
|
66
|
-
Thread.current.abort_on_exception = false
|
67
|
-
monitor
|
68
|
-
end
|
66
|
+
@thread = start_monitoring_thread
|
69
67
|
end
|
70
68
|
|
71
69
|
# Set the state to `:closing` and wait for the state to be set to `:closed`
|
@@ -84,10 +82,17 @@ module ProcessExecuter
|
|
84
82
|
# @return [void]
|
85
83
|
#
|
86
84
|
def close
|
87
|
-
|
85
|
+
mutex.synchronize do
|
86
|
+
return unless state == :open
|
88
87
|
|
89
|
-
|
90
|
-
|
88
|
+
@state = :closing
|
89
|
+
end
|
90
|
+
|
91
|
+
mutex.synchronize do
|
92
|
+
condition_variable.wait(mutex) while @state != :closed
|
93
|
+
end
|
94
|
+
|
95
|
+
thread.join
|
91
96
|
|
92
97
|
destination.close
|
93
98
|
end
|
@@ -152,9 +157,11 @@ module ProcessExecuter
|
|
152
157
|
# @api private
|
153
158
|
#
|
154
159
|
def write(data)
|
155
|
-
|
160
|
+
mutex.synchronize do
|
161
|
+
raise IOError, 'closed stream' unless state == :open
|
156
162
|
|
157
|
-
|
163
|
+
pipe_writer.write(data)
|
164
|
+
end
|
158
165
|
end
|
159
166
|
|
160
167
|
# @!attribute [r]
|
@@ -255,6 +262,28 @@ module ProcessExecuter
|
|
255
262
|
|
256
263
|
private
|
257
264
|
|
265
|
+
# @!attribute [r]
|
266
|
+
#
|
267
|
+
# The mutex used to synchronize access to the state variable
|
268
|
+
#
|
269
|
+
# @return [Mutex]
|
270
|
+
#
|
271
|
+
# @api private
|
272
|
+
#
|
273
|
+
attr_reader :mutex
|
274
|
+
|
275
|
+
# @!attribute [r]
|
276
|
+
#
|
277
|
+
# The condition variable used to synchronize access to the state
|
278
|
+
#
|
279
|
+
# In particular, it is used while waiting for the state to change to :closed
|
280
|
+
#
|
281
|
+
# @return [ConditionVariable]
|
282
|
+
#
|
283
|
+
# @api private
|
284
|
+
#
|
285
|
+
attr_reader :condition_variable
|
286
|
+
|
258
287
|
# Raise an error if the destination is not compatible with MonitoredPipe
|
259
288
|
# @return [void]
|
260
289
|
# @raise [ArgumentError] if the destination is not compatible with MonitoredPipe
|
@@ -265,6 +294,17 @@ module ProcessExecuter
|
|
265
294
|
raise ArgumentError, "Destination #{destination.destination} is not compatible with MonitoredPipe"
|
266
295
|
end
|
267
296
|
|
297
|
+
# Start the thread to monitor the pipe and write data to the destination
|
298
|
+
# @return [void]
|
299
|
+
# @api private
|
300
|
+
def start_monitoring_thread
|
301
|
+
Thread.new do
|
302
|
+
Thread.current.report_on_exception = false
|
303
|
+
Thread.current.abort_on_exception = false
|
304
|
+
monitor
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
268
308
|
# Read data from the pipe until `#state` is changed to `:closing`
|
269
309
|
#
|
270
310
|
# The state is changed to `:closed` by calling `#close`.
|
@@ -275,8 +315,12 @@ module ProcessExecuter
|
|
275
315
|
# @api private
|
276
316
|
def monitor
|
277
317
|
monitor_pipe until state == :closing
|
318
|
+
ensure
|
278
319
|
close_pipe
|
279
|
-
|
320
|
+
mutex.synchronize do
|
321
|
+
@state = :closed
|
322
|
+
condition_variable.signal
|
323
|
+
end
|
280
324
|
end
|
281
325
|
|
282
326
|
# Read data from the pipe until `#state` is changed to `:closing`
|
@@ -310,8 +354,10 @@ module ProcessExecuter
|
|
310
354
|
def write_data(data)
|
311
355
|
destination.write(data)
|
312
356
|
rescue StandardError => e
|
313
|
-
|
314
|
-
|
357
|
+
mutex.synchronize do
|
358
|
+
@exception = e
|
359
|
+
@state = :closing
|
360
|
+
end
|
315
361
|
end
|
316
362
|
|
317
363
|
# Read any remaining data from the pipe and close it
|
@@ -42,8 +42,7 @@ module ProcessExecuter
|
|
42
42
|
# @param command [Array<String>] The command to execute
|
43
43
|
# @param options [ProcessExecuter::Options::RunOptions] Options for running the command
|
44
44
|
#
|
45
|
-
# @raise [ProcessExecuter::
|
46
|
-
# @raise [ProcessExecuter::TimeoutError] If the command times out
|
45
|
+
# @raise [ProcessExecuter::Error] if the command could not be executed or failed
|
47
46
|
#
|
48
47
|
# @return [ProcessExecuter::Result] The result of the completed subprocess
|
49
48
|
#
|
@@ -100,10 +99,7 @@ module ProcessExecuter
|
|
100
99
|
#
|
101
100
|
# @return [Void]
|
102
101
|
#
|
103
|
-
# @raise [ProcessExecuter::
|
104
|
-
# @raise [ProcessExecuter::SignaledError] If the command was signaled
|
105
|
-
# @raise [ProcessExecuter::TimeoutError] If the command times out
|
106
|
-
# @raise [ProcessExecuter::ProcessIOError] If an exception was raised while collecting subprocess output
|
102
|
+
# @raise [ProcessExecuter::Error] if the command could not be executed or failed
|
107
103
|
#
|
108
104
|
# @api private
|
109
105
|
#
|
data/lib/process_executer.rb
CHANGED
@@ -87,7 +87,11 @@ module ProcessExecuter
|
|
87
87
|
# @return [ProcessExecuter::Result] The result of the completed subprocess
|
88
88
|
# @api private
|
89
89
|
def self.spawn_and_wait_with_options(command, options)
|
90
|
-
|
90
|
+
begin
|
91
|
+
pid = Process.spawn(*command, **options.spawn_options)
|
92
|
+
rescue StandardError => e
|
93
|
+
raise ProcessExecuter::SpawnError, "Failed to spawn process: #{e.message}"
|
94
|
+
end
|
91
95
|
wait_for_process(pid, command, options)
|
92
96
|
end
|
93
97
|
|
@@ -287,10 +291,7 @@ module ProcessExecuter
|
|
287
291
|
# @option options_hash [String] :chdir (nil) The directory to run the command in
|
288
292
|
# @option options_hash [Logger] :logger The logger to use
|
289
293
|
#
|
290
|
-
# @raise [ProcessExecuter::
|
291
|
-
# @raise [ProcessExecuter::SignaledError] if the command exited because of an unhandled signal
|
292
|
-
# @raise [ProcessExecuter::TimeoutError] if the command timed out
|
293
|
-
# @raise [ProcessExecuter::ProcessIOError] if an exception was raised while collecting subprocess output
|
294
|
+
# @raise [ProcessExecuter::Error] if the command could not be executed or failed
|
294
295
|
#
|
295
296
|
# @return [ProcessExecuter::Result] The result of the completed subprocess
|
296
297
|
#
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process_executer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Couball
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-04-05 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: bundler-audit
|
@@ -250,8 +250,8 @@ metadata:
|
|
250
250
|
allowed_push_host: https://rubygems.org
|
251
251
|
homepage_uri: https://github.com/main-branch/process_executer
|
252
252
|
source_code_uri: https://github.com/main-branch/process_executer
|
253
|
-
documentation_uri: https://rubydoc.info/gems/process_executer/3.
|
254
|
-
changelog_uri: https://rubydoc.info/gems/process_executer/3.
|
253
|
+
documentation_uri: https://rubydoc.info/gems/process_executer/3.2.0
|
254
|
+
changelog_uri: https://rubydoc.info/gems/process_executer/3.2.0/file/CHANGELOG.md
|
255
255
|
rubygems_mfa_required: 'true'
|
256
256
|
rdoc_options: []
|
257
257
|
require_paths:
|