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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc6b432be7a421bc53561149ce4718a41fd7ff61b1b2213498000891c388a0c9
4
- data.tar.gz: ce582e04a3500b0b6c0e31af16d2d735e87f9c99c5d82279c591c9e5abc0a619
3
+ metadata.gz: c6d2904b0fd36166eb05a23f8d208b27de0c19bc10284e71e63a1e7055034a7b
4
+ data.tar.gz: 5ed998bd38182774d63265bd6729c219918acf41755215a172c87b85e4873ebe
5
5
  SHA512:
6
- metadata.gz: 312c65d62033f2e3a13ed5094de32940b9923d775d45f3d90dc6cbe4f0a8d0dcf3e5e772e6add2999c97e2b2980b39d4fa3d4f7ddf0172e9877d7e94a1770d94
7
- data.tar.gz: 267da77563194721787fee28ff41cdf19159f083f5a0cb04d6d912e0271f2e2062b50da8d1655b36bea2ebb5327f3f3d618aa90453ad60ba61d72e4c779afe84
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: 1000)
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 { monitor }
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
- if pipe_reader.wait_readable(0.01)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module ProcessExecuter
4
4
  # The current Gem version
5
- VERSION = '0.4.0'
5
+ VERSION = '0.5.0'
6
6
  end
@@ -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 or the timeout has been reached.
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 for this execution context
44
+ # @param options_hash [Hash] the options to use when exectuting the command
38
45
  #
39
- # @return [ProcessExecuter::ExecutionContext] the execution context that can run commands
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 = ::Process.spawn(*command, **options.spawn_options)
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 [ProcessExecuter::Status] the status of the process
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
- ::Process.wait2(pid).last
67
+ Process.wait2(pid).last
61
68
  end
62
69
  rescue Timeout::Error
63
- ::Process.kill('KILL', pid)
64
- ::Process.wait2(pid).last
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.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-07 00:00:00.000000000 Z
11
+ date: 2022-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bump