process_executer 0.4.0 → 0.6.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: 9ac0d1397bbe7c4f632a017f4b66ccdfba6860eed0bb616e65b385ef43bcd613
4
+ data.tar.gz: 3b4430d7c79739767bcf8f24ab644834eb4a43e89876ef27c1b40032621b72e9
5
5
  SHA512:
6
- metadata.gz: 312c65d62033f2e3a13ed5094de32940b9923d775d45f3d90dc6cbe4f0a8d0dcf3e5e772e6add2999c97e2b2980b39d4fa3d4f7ddf0172e9877d7e94a1770d94
7
- data.tar.gz: 267da77563194721787fee28ff41cdf19159f083f5a0cb04d6d912e0271f2e2062b50da8d1655b36bea2ebb5327f3f3d618aa90453ad60ba61d72e4c779afe84
6
+ metadata.gz: 63766977ee0f62f6f8ef705c5bc47ecc5358bf06bb9764245ca7c4d6114adf7d0cfef443fac5c89597b5f504ef0b52d8444cf03436c8aae00c0e6ed6fc1d22ad
7
+ data.tar.gz: 1e8d38c8b4a1169177cd41a8767da33c2b15e2c7a45c3d7ef042aeafc7ba46b19bebfc63ef1bc0b8cd781b312300fbf51363b2305207d0d752895dd0a0dca735
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
+ ## v0.6.0 (2023-02-12)
9
+
10
+ [Full Changelog](https://github.com/main-branch/process_executer/compare/v0.5.0..v0.6.0)
11
+
12
+ Changes since v0.5.0:
13
+
14
+ * 2a22dbd Fix intermittent test failures (#21)
15
+ * e3afaa3 Add build for MRI Ruby 3.2 on unbuntu-latest (#20)
16
+ * 17522ac Use latest create_release_version gem (#19)
17
+ * ba1fb2d Read remaining data from pipe_reader when closing a MonitoredPipe (#17)
18
+ * 8422aa9 Release v0.5.0
19
+
20
+ ## v0.5.0 (2022-12-12)
21
+
22
+ [Full Changelog](https://github.com/main-branch/process_executer/compare/v0.4.0...v0.5.0)
23
+
24
+ * c6d8de9 Workaround a problem with SimpleCov / JRuby
25
+ * c480b5f Increase time to wait for results from a writer throwing an exception
26
+ * 1934563 Handle exceptions from writers within MonitoredPipe
27
+ * e948ada Increase default chunk_size to 100_000 bytes
28
+ * 5eb2c24 Update documentation for ProcessExecuter#spawn
29
+ * a3a4217 Release v0.4.0
30
+
8
31
  ## v0.4.0 (2022-12-06)
9
32
 
10
33
  [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
@@ -270,11 +299,11 @@ module ProcessExecuter
270
299
  def close_pipe
271
300
  # Close the write end of the pipe so no more data can be written to it
272
301
  pipe_writer.close
302
+
273
303
  # Read remaining data from pipe_reader (if any)
274
- if pipe_reader.wait_readable(0.01)
275
- new_data = pipe_reader.read(chunk_size)
276
- writers.each { |w| w.write(new_data) }
277
- end
304
+ # If an exception was already raised by the last call to #write, then don't try to read remaining data
305
+ monitor_pipe while exception.nil? && !pipe_reader.eof?
306
+
278
307
  # Close the read end of the pipe
279
308
  pipe_reader.close
280
309
  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.6.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
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
35
35
  # spec.add_dependency "example-gem", "~> 1.0"
36
36
  spec.add_development_dependency 'bump', '~> 0.10'
37
37
  spec.add_development_dependency 'bundler-audit', '~> 0.9'
38
- spec.add_development_dependency 'create_github_release', '~> 0.2'
38
+ spec.add_development_dependency 'create_github_release', '~> 1.0'
39
39
  spec.add_development_dependency 'rake', '~> 13.0'
40
40
  spec.add_development_dependency 'rspec', '~> 3.10'
41
41
  spec.add_development_dependency 'rubocop', '~> 1.36'
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.6.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: 2023-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bump
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.2'
47
+ version: '1.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.2'
54
+ version: '1.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -229,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
229
  - !ruby/object:Gem::Version
230
230
  version: '0'
231
231
  requirements: []
232
- rubygems_version: 3.3.7
232
+ rubygems_version: 3.4.1
233
233
  signing_key:
234
234
  specification_version: 4
235
235
  summary: An API for executing commands in a subprocess