process_executer 0.4.0 → 0.5.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 +11 -0
- data/lib/process_executer/monitored_pipe.rb +35 -5
- data/lib/process_executer/version.rb +1 -1
- data/lib/process_executer.rb +17 -10
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6d2904b0fd36166eb05a23f8d208b27de0c19bc10284e71e63a1e7055034a7b
|
4
|
+
data.tar.gz: 5ed998bd38182774d63265bd6729c219918acf41755215a172c87b85e4873ebe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7aec2cdf98b06acccccfa449e57efbc3d6ea4b7bb9ee833a5f81ee77faa02490c55d5c3cea7c1235295d765eaf3b957b2577e3cf8fae0818d4e60e097218bcc3
|
7
|
+
data.tar.gz: a0a95d10b07111be5ca3bc094cfa4255629f43b723fb7ae44ead94615c11cd97af370da9a219e481c1d55eb55fd20f7f3a24e213a96834d91b3f18527a6af8ab
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,17 @@ 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
|
+
## v0.5.0 (2022-12-12)
|
9
|
+
|
10
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v0.4.0...v0.5.0)
|
11
|
+
|
12
|
+
* c6d8de9 Workaround a problem with SimpleCov / JRuby
|
13
|
+
* c480b5f Increase time to wait for results from a writer throwing an exception
|
14
|
+
* 1934563 Handle exceptions from writers within MonitoredPipe
|
15
|
+
* e948ada Increase default chunk_size to 100_000 bytes
|
16
|
+
* 5eb2c24 Update documentation for ProcessExecuter#spawn
|
17
|
+
* a3a4217 Release v0.4.0
|
18
|
+
|
8
19
|
## v0.4.0 (2022-12-06)
|
9
20
|
|
10
21
|
[Full Changelog](https://github.com/main-branch/process_executer/compare/v0.3.0...v0.4.0)
|
@@ -12,6 +12,9 @@ module ProcessExecuter
|
|
12
12
|
# Data that is read from that pipe is written one or more writers passed to
|
13
13
|
# `#initialize`.
|
14
14
|
#
|
15
|
+
# If any of the writers raise an exception, the monitoring thread will exit, the
|
16
|
+
# pipe will be closed, and the exception will be saved in `#exception`.
|
17
|
+
#
|
15
18
|
# `#close` must be called to ensure that (1) the pipe is closed, (2) all data is
|
16
19
|
# read from the pipe and written to the writers, and (3) the monitoring thread is
|
17
20
|
# killed.
|
@@ -52,12 +55,16 @@ module ProcessExecuter
|
|
52
55
|
# @param writers [Array<#write>] as data is read from the pipe, it is written to these writers
|
53
56
|
# @param chunk_size [Integer] the size of the chunks to read from the pipe
|
54
57
|
#
|
55
|
-
def initialize(*writers, chunk_size:
|
58
|
+
def initialize(*writers, chunk_size: 100_000)
|
56
59
|
@writers = writers
|
57
60
|
@chunk_size = chunk_size
|
58
61
|
@pipe_reader, @pipe_writer = IO.pipe
|
59
62
|
@state = :open
|
60
|
-
@thread = Thread.new
|
63
|
+
@thread = Thread.new do
|
64
|
+
Thread.current.report_on_exception = false
|
65
|
+
Thread.current.abort_on_exception = false
|
66
|
+
monitor
|
67
|
+
end
|
61
68
|
end
|
62
69
|
|
63
70
|
# Set the state to `:closing` and wait for the state to be set to `:closed`
|
@@ -76,6 +83,8 @@ module ProcessExecuter
|
|
76
83
|
# @return [void]
|
77
84
|
#
|
78
85
|
def close
|
86
|
+
return unless state == :open
|
87
|
+
|
79
88
|
@state = :closing
|
80
89
|
sleep 0.01 until state == :closed
|
81
90
|
end
|
@@ -233,6 +242,19 @@ module ProcessExecuter
|
|
233
242
|
#
|
234
243
|
attr_reader :state
|
235
244
|
|
245
|
+
# @!attribute [r]
|
246
|
+
#
|
247
|
+
# The exception raised by a writer
|
248
|
+
#
|
249
|
+
# If an exception is raised by a writer, it is stored here. Otherwise, it is `nil`.
|
250
|
+
#
|
251
|
+
# @example
|
252
|
+
# pipe.exception #=> nil
|
253
|
+
#
|
254
|
+
# @return [Exception, nil] the exception raised by a writer or `nil` if no exception was raised
|
255
|
+
#
|
256
|
+
attr_reader :exception
|
257
|
+
|
236
258
|
private
|
237
259
|
|
238
260
|
# Read data from the pipe until `#state` is changed to `:closing`
|
@@ -257,10 +279,17 @@ module ProcessExecuter
|
|
257
279
|
# @api private
|
258
280
|
def monitor_pipe
|
259
281
|
new_data = pipe_reader.read_nonblock(chunk_size)
|
282
|
+
# SimpleCov under JRuby reports the begin statement as not covered, but it is
|
283
|
+
# :nocov:
|
284
|
+
begin
|
285
|
+
# :nocov:
|
286
|
+
writers.each { |w| w.write(new_data) }
|
287
|
+
rescue StandardError => e
|
288
|
+
@exception = e
|
289
|
+
@state = :closing
|
290
|
+
end
|
260
291
|
rescue IO::WaitReadable
|
261
292
|
pipe_reader.wait_readable(0.01)
|
262
|
-
else
|
263
|
-
writers.each { |w| w.write(new_data) }
|
264
293
|
end
|
265
294
|
|
266
295
|
# Read any remaining data from the pipe and close it
|
@@ -271,7 +300,8 @@ module ProcessExecuter
|
|
271
300
|
# Close the write end of the pipe so no more data can be written to it
|
272
301
|
pipe_writer.close
|
273
302
|
# Read remaining data from pipe_reader (if any)
|
274
|
-
|
303
|
+
# If an exception was already raised by the last call to #write, then don't try to read remaining data
|
304
|
+
if exception.nil? && pipe_reader.wait_readable(0.01)
|
275
305
|
new_data = pipe_reader.read(chunk_size)
|
276
306
|
writers.each { |w| w.write(new_data) }
|
277
307
|
end
|
data/lib/process_executer.rb
CHANGED
@@ -10,15 +10,17 @@ require 'timeout'
|
|
10
10
|
# @api public
|
11
11
|
#
|
12
12
|
module ProcessExecuter
|
13
|
-
# Execute the specified command and return the exit status
|
13
|
+
# Execute the specified command as a subprocess and return the exit status
|
14
14
|
#
|
15
|
-
# This method blocks until the command has terminated
|
15
|
+
# This method blocks until the command has terminated.
|
16
|
+
#
|
17
|
+
# The command will be send the SIGKILL signal if it does not terminate within
|
18
|
+
# the specified timeout.
|
16
19
|
#
|
17
20
|
# @example
|
18
21
|
# status = ProcessExecuter.spawn('echo hello')
|
19
22
|
# status.exited? # => true
|
20
23
|
# status.success? # => true
|
21
|
-
# stdout.string # => "hello\n"
|
22
24
|
#
|
23
25
|
# @example with a timeout
|
24
26
|
# status = ProcessExecuter.spawn('sleep 10', timeout: 0.01)
|
@@ -27,6 +29,11 @@ module ProcessExecuter
|
|
27
29
|
# status.signaled? # => true
|
28
30
|
# status.termsig # => 9
|
29
31
|
#
|
32
|
+
# @example capturing stdout to a string
|
33
|
+
# stdout = StringIO.new
|
34
|
+
# status = ProcessExecuter.spawn('echo hello', out: stdout)
|
35
|
+
# stdout.string # => "hello"
|
36
|
+
#
|
30
37
|
# @see https://ruby-doc.org/core-3.1.2/Kernel.html#method-i-spawn Kernel.spawn
|
31
38
|
# documentation for valid command and options
|
32
39
|
#
|
@@ -34,13 +41,13 @@ module ProcessExecuter
|
|
34
41
|
# for additional options that may be specified
|
35
42
|
#
|
36
43
|
# @param command [Array<String>] the command to execute
|
37
|
-
# @param options_hash [Hash] the options to use
|
44
|
+
# @param options_hash [Hash] the options to use when exectuting the command
|
38
45
|
#
|
39
|
-
# @return [
|
46
|
+
# @return [Process::Status] the exit status of the proceess
|
40
47
|
#
|
41
48
|
def self.spawn(*command, **options_hash)
|
42
49
|
options = ProcessExecuter::Options.new(**options_hash)
|
43
|
-
pid =
|
50
|
+
pid = Process.spawn(*command, **options.spawn_options)
|
44
51
|
wait_for_process(pid, options)
|
45
52
|
end
|
46
53
|
|
@@ -51,16 +58,16 @@ module ProcessExecuter
|
|
51
58
|
# @param pid [Integer] the process id
|
52
59
|
# @param options [ProcessExecuter::Options] the options used
|
53
60
|
#
|
54
|
-
# @return [
|
61
|
+
# @return [Process::Status] the status of the process
|
55
62
|
#
|
56
63
|
# @api private
|
57
64
|
#
|
58
65
|
private_class_method def self.wait_for_process(pid, options)
|
59
66
|
Timeout.timeout(options.timeout) do
|
60
|
-
|
67
|
+
Process.wait2(pid).last
|
61
68
|
end
|
62
69
|
rescue Timeout::Error
|
63
|
-
|
64
|
-
|
70
|
+
Process.kill('KILL', pid)
|
71
|
+
Process.wait2(pid).last
|
65
72
|
end
|
66
73
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: process_executer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Couball
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-12-
|
11
|
+
date: 2022-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bump
|