process_executer 3.1.0 → 3.2.1
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 +23 -0
- data/lib/process_executer/monitored_pipe.rb +62 -14
- data/lib/process_executer/runner.rb +4 -11
- data/lib/process_executer/version.rb +1 -1
- data/process_executer.gemspec +2 -0
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a9debe1b364e7755e593434f5b018b6ef39d64f509132b2f803ebe1be1bfe36
|
4
|
+
data.tar.gz: 780091f37f642962c1bbaf885cd22559defe52d568294884df23aac0eb575318
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc21311fca209fc28ce413c7562a8d80a3268c90bf34941525422db8cdd0656549c5e8ca285c4cfee4711f0d9ad8babe4de55676d4ed0fbf77f2001b4ebd17fe
|
7
|
+
data.tar.gz: 689feaf01cc890fcb978c78e24c16ad912e4e64cfa3494ecd6d825ceaf86662d46e8239528e4f985ed9a5365cc32498c7d618d1203aebf1da0a34bfedf868228
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,29 @@ 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.1 (2025-04-08)
|
9
|
+
|
10
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v3.2.0..v3.2.1)
|
11
|
+
|
12
|
+
Changes since v3.2.0:
|
13
|
+
|
14
|
+
* d1e19a5 test: assert that MonitoredPipe has no open instances after each test
|
15
|
+
* aa71f8e test: ensure MonitoredPipe cleans up open instances in specs
|
16
|
+
* 987b0c9 fix: ensure that all pipes are closed even when there is an IOError
|
17
|
+
* 65e8db0 fix: ensure that MonitoredPipe cleans up after itself even when there is IOError
|
18
|
+
* ed2454e chore: integrate track_open_instances gem to report on leaked MonitoredPipe instances
|
19
|
+
* f25c87d chore: release v3.2.0
|
20
|
+
|
21
|
+
## v3.2.0 (2025-04-04)
|
22
|
+
|
23
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v3.1.0..v3.2.0)
|
24
|
+
|
25
|
+
Changes since v3.1.0:
|
26
|
+
|
27
|
+
* 272d246 test: fix flaky test that fails on windows
|
28
|
+
* 1e121d8 test: add test for raising a SpawnError when Process.spawn raises an error
|
29
|
+
* 2a2aaac refactor: improve synchronization of the monitored pipe state
|
30
|
+
|
8
31
|
## v3.1.0 (2025-04-01)
|
9
32
|
|
10
33
|
[Full Changelog](https://github.com/main-branch/process_executer/compare/v3.0.0..v3.1.0)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'stringio'
|
4
4
|
require 'io/wait'
|
5
|
+
require 'track_open_instances'
|
5
6
|
|
6
7
|
module ProcessExecuter
|
7
8
|
# Write data sent through a pipe to a destination
|
@@ -41,6 +42,8 @@ module ProcessExecuter
|
|
41
42
|
# @api public
|
42
43
|
#
|
43
44
|
class MonitoredPipe
|
45
|
+
include TrackOpenInstances
|
46
|
+
|
44
47
|
# Create a new monitored pipe
|
45
48
|
#
|
46
49
|
# Creates a IO.pipe and starts a monitoring thread to read data written to the pipe.
|
@@ -58,14 +61,14 @@ module ProcessExecuter
|
|
58
61
|
|
59
62
|
assert_destination_is_compatible_with_monitored_pipe
|
60
63
|
|
64
|
+
@mutex = Mutex.new
|
65
|
+
@condition_variable = ConditionVariable.new
|
61
66
|
@chunk_size = chunk_size
|
62
67
|
@pipe_reader, @pipe_writer = IO.pipe
|
63
68
|
@state = :open
|
64
|
-
@thread =
|
65
|
-
|
66
|
-
|
67
|
-
monitor
|
68
|
-
end
|
69
|
+
@thread = start_monitoring_thread
|
70
|
+
|
71
|
+
self.class.add_open_instance(self)
|
69
72
|
end
|
70
73
|
|
71
74
|
# Set the state to `:closing` and wait for the state to be set to `:closed`
|
@@ -84,12 +87,16 @@ module ProcessExecuter
|
|
84
87
|
# @return [void]
|
85
88
|
#
|
86
89
|
def close
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
mutex.synchronize do
|
91
|
+
if state == :open
|
92
|
+
@state = :closing
|
93
|
+
condition_variable.wait(mutex) while @state != :closed
|
94
|
+
end
|
95
|
+
end
|
91
96
|
|
97
|
+
thread.join
|
92
98
|
destination.close
|
99
|
+
self.class.remove_open_instance(self)
|
93
100
|
end
|
94
101
|
|
95
102
|
# Return the write end of the pipe so that data can be written to it
|
@@ -152,9 +159,11 @@ module ProcessExecuter
|
|
152
159
|
# @api private
|
153
160
|
#
|
154
161
|
def write(data)
|
155
|
-
|
162
|
+
mutex.synchronize do
|
163
|
+
raise IOError, 'closed stream' unless state == :open
|
156
164
|
|
157
|
-
|
165
|
+
pipe_writer.write(data)
|
166
|
+
end
|
158
167
|
end
|
159
168
|
|
160
169
|
# @!attribute [r]
|
@@ -255,6 +264,28 @@ module ProcessExecuter
|
|
255
264
|
|
256
265
|
private
|
257
266
|
|
267
|
+
# @!attribute [r]
|
268
|
+
#
|
269
|
+
# The mutex used to synchronize access to the state variable
|
270
|
+
#
|
271
|
+
# @return [Mutex]
|
272
|
+
#
|
273
|
+
# @api private
|
274
|
+
#
|
275
|
+
attr_reader :mutex
|
276
|
+
|
277
|
+
# @!attribute [r]
|
278
|
+
#
|
279
|
+
# The condition variable used to synchronize access to the state
|
280
|
+
#
|
281
|
+
# In particular, it is used while waiting for the state to change to :closed
|
282
|
+
#
|
283
|
+
# @return [ConditionVariable]
|
284
|
+
#
|
285
|
+
# @api private
|
286
|
+
#
|
287
|
+
attr_reader :condition_variable
|
288
|
+
|
258
289
|
# Raise an error if the destination is not compatible with MonitoredPipe
|
259
290
|
# @return [void]
|
260
291
|
# @raise [ArgumentError] if the destination is not compatible with MonitoredPipe
|
@@ -265,6 +296,17 @@ module ProcessExecuter
|
|
265
296
|
raise ArgumentError, "Destination #{destination.destination} is not compatible with MonitoredPipe"
|
266
297
|
end
|
267
298
|
|
299
|
+
# Start the thread to monitor the pipe and write data to the destination
|
300
|
+
# @return [void]
|
301
|
+
# @api private
|
302
|
+
def start_monitoring_thread
|
303
|
+
Thread.new do
|
304
|
+
Thread.current.report_on_exception = false
|
305
|
+
Thread.current.abort_on_exception = false
|
306
|
+
monitor
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
268
310
|
# Read data from the pipe until `#state` is changed to `:closing`
|
269
311
|
#
|
270
312
|
# The state is changed to `:closed` by calling `#close`.
|
@@ -275,8 +317,12 @@ module ProcessExecuter
|
|
275
317
|
# @api private
|
276
318
|
def monitor
|
277
319
|
monitor_pipe until state == :closing
|
320
|
+
ensure
|
278
321
|
close_pipe
|
279
|
-
|
322
|
+
mutex.synchronize do
|
323
|
+
@state = :closed
|
324
|
+
condition_variable.signal
|
325
|
+
end
|
280
326
|
end
|
281
327
|
|
282
328
|
# Read data from the pipe until `#state` is changed to `:closing`
|
@@ -310,8 +356,10 @@ module ProcessExecuter
|
|
310
356
|
def write_data(data)
|
311
357
|
destination.write(data)
|
312
358
|
rescue StandardError => e
|
313
|
-
|
314
|
-
|
359
|
+
mutex.synchronize do
|
360
|
+
@exception = e
|
361
|
+
@state = :closing
|
362
|
+
end
|
315
363
|
end
|
316
364
|
|
317
365
|
# Read any remaining data from the pipe and close it
|
@@ -52,7 +52,8 @@ module ProcessExecuter
|
|
52
52
|
opened_pipes = wrap_stdout_stderr(options)
|
53
53
|
ProcessExecuter.spawn_and_wait_with_options(command, options)
|
54
54
|
ensure
|
55
|
-
opened_pipes.
|
55
|
+
opened_pipes.each_value(&:close)
|
56
|
+
opened_pipes.each { |option_key, pipe| raise_pipe_error(command, option_key, pipe) }
|
56
57
|
end
|
57
58
|
|
58
59
|
# Wrap the stdout and stderr redirection options with a MonitoredPipe
|
@@ -81,16 +82,6 @@ module ProcessExecuter
|
|
81
82
|
ProcessExecuter::Destinations.compatible_with_monitored_pipe?(value)
|
82
83
|
end
|
83
84
|
|
84
|
-
# Close the pipe and raise an error if the pipe raised an exception
|
85
|
-
# @return [void]
|
86
|
-
# @raise [ProcessExecuter::ProcessIOError] If an exception was raised while
|
87
|
-
# collecting subprocess output
|
88
|
-
# @api private
|
89
|
-
def close_pipe(command, option_key, pipe)
|
90
|
-
pipe.close
|
91
|
-
raise_pipe_error(command, option_key, pipe) if pipe.exception
|
92
|
-
end
|
93
|
-
|
94
85
|
# Process the result of the command and return a ProcessExecuter::Result
|
95
86
|
#
|
96
87
|
# Log the command and result, and raise an error if the command failed.
|
@@ -144,6 +135,8 @@ module ProcessExecuter
|
|
144
135
|
# @api private
|
145
136
|
#
|
146
137
|
def raise_pipe_error(command, option_key, pipe)
|
138
|
+
return unless pipe.exception
|
139
|
+
|
147
140
|
error = ProcessExecuter::ProcessIOError.new("Pipe Exception for #{command}: #{option_key.inspect}")
|
148
141
|
raise(error, cause: pipe.exception)
|
149
142
|
end
|
data/process_executer.gemspec
CHANGED
@@ -37,6 +37,8 @@ Gem::Specification.new do |spec|
|
|
37
37
|
'Ruby: MRI 3.1 or later, TruffleRuby 24 or later, or JRuby 9.4 or later'
|
38
38
|
]
|
39
39
|
|
40
|
+
spec.add_dependency 'track_open_instances', '~> 0.1'
|
41
|
+
|
40
42
|
spec.add_development_dependency 'bundler-audit', '~> 0.9'
|
41
43
|
spec.add_development_dependency 'create_github_release', '~> 2.1'
|
42
44
|
spec.add_development_dependency 'main_branch_shared_rubocop_config', '~> 0.1'
|
metadata
CHANGED
@@ -1,14 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process_executer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1
|
4
|
+
version: 3.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Couball
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-04-
|
10
|
+
date: 2025-04-09 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: track_open_instances
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.1'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.1'
|
12
26
|
- !ruby/object:Gem::Dependency
|
13
27
|
name: bundler-audit
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -250,8 +264,8 @@ metadata:
|
|
250
264
|
allowed_push_host: https://rubygems.org
|
251
265
|
homepage_uri: https://github.com/main-branch/process_executer
|
252
266
|
source_code_uri: https://github.com/main-branch/process_executer
|
253
|
-
documentation_uri: https://rubydoc.info/gems/process_executer/3.1
|
254
|
-
changelog_uri: https://rubydoc.info/gems/process_executer/3.1
|
267
|
+
documentation_uri: https://rubydoc.info/gems/process_executer/3.2.1
|
268
|
+
changelog_uri: https://rubydoc.info/gems/process_executer/3.2.1/file/CHANGELOG.md
|
255
269
|
rubygems_mfa_required: 'true'
|
256
270
|
rdoc_options: []
|
257
271
|
require_paths:
|