process-group 1.0.1 → 1.2.3

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
- SHA1:
3
- metadata.gz: 28a68aa53cd296412dd77cdca5a9c8b000269439
4
- data.tar.gz: 49777327e43bbddda6e17a2b13776227693d115d
2
+ SHA256:
3
+ metadata.gz: a2ef7e85dc264aa4d6254170467cd4fc1e47d2c5f552b3c55528370142526452
4
+ data.tar.gz: 7e8d6ccb53689d31a427484cfab31bb783a6896de2208c2780f664f3b6e5e883
5
5
  SHA512:
6
- metadata.gz: 508765b5658d4fbb032c7f43f36a5a9c26e4fbbfc379295398d3ac08e9da12bab9440cb9002bd052d9eaaba5abbbd9ee970f8a290ceaab144ad682e53e95ebfb
7
- data.tar.gz: 0f7d655bd81259b92c757342ff71aee743209e33dbb3c1a71e4b4facd0c4dac5de706a01735c4afd964feb6b52429e8d6b07e706db992408a7da81f599b5a4e9
6
+ metadata.gz: 2106bb6e1e7f2f7f20a63781d82b95786305b53005bdff44cd0bdc5e1a2ff13c6efefc26c715f3d76919b683309afac6016e18dbdbc374afb736a36c9b1c4a1c
7
+ data.tar.gz: 8e96440ab717809e09c06f11ea3e9bb8596f31613804b04a11a84266a55008fe22d42a47129798062cad0ebc9dd54dc3ea0652664a36b535a0cc3ae4d4f1c1af
@@ -19,75 +19,107 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require 'fiber'
22
+ require 'process/terminal'
22
23
 
23
24
  module Process
24
25
  # A group of tasks which can be run asynchrnously using fibers. Someone must call Group#wait to ensure that all fibers eventually resume.
25
26
  class Group
26
- # Executes a command using Process.spawn with the given arguments and options.
27
+ def self.wait(**options, &block)
28
+ group = Group.new(**options)
29
+
30
+ group.wait(&block)
31
+ end
32
+
27
33
  class Command
28
- def initialize(arguments, options, fiber = Fiber.current)
29
- @arguments = arguments
34
+ def initialize(foreground: false, **options)
30
35
  @options = options
31
-
32
- @fiber = fiber
36
+ @foreground = foreground
37
+
38
+ @fiber = Fiber.current
39
+ @pid = nil
33
40
  end
34
-
35
- attr :arguments
41
+
36
42
  attr :options
37
-
38
- def run(options = {})
39
- @pid = Process.spawn(*@arguments, @options.merge(options))
40
-
41
- return @pid
43
+
44
+ attr :pid
45
+
46
+ def foreground?
47
+ @foreground
42
48
  end
43
-
49
+
44
50
  def resume(*arguments)
45
51
  @fiber.resume(*arguments)
46
52
  end
53
+
54
+ def kill(signal = :INT)
55
+ Process.kill(signal, @pid)
56
+ end
47
57
  end
48
58
 
49
- # Runs a given block using a forked process.
50
- class Fork
51
- def initialize(block, options, fiber = Fiber.current)
52
- @options = options
59
+ # Executes a command using Process.spawn with the given arguments and options.
60
+ class Spawn < Command
61
+ def initialize(arguments, **options)
62
+ @arguments = arguments
63
+
64
+ super(**options)
65
+ end
66
+
67
+ attr :arguments
68
+
69
+ def call(**options)
70
+ options = @options.merge(options)
53
71
 
54
- raise ArgumentError.new("Fork requires a block!") unless @block = block
72
+ @pid = Process.spawn(*@arguments, **options)
73
+ end
74
+ end
75
+
76
+ # Runs a given block using a forked process.
77
+ class Fork < Command
78
+ def initialize(block, **options)
79
+ @block = block
55
80
 
56
- @fiber = fiber
81
+ super(**options)
57
82
  end
58
83
 
59
- def run(options = {})
84
+ def call(**options)
85
+ options = @options.merge(options)
86
+
60
87
  @pid = Process.fork(&@block)
61
88
 
62
89
  if options[:pgroup] == true
63
90
  # Establishes the child process as a process group leader:
64
91
  Process.setpgid(@pid, 0)
65
- else
92
+ elsif pgroup = options[:pgroup]
66
93
  # Set this process as part of the existing process group:
67
- Process.setpgid(@pid, options[:pgroup])
94
+ Process.setpgid(@pid, pgroup)
68
95
  end
69
96
 
70
97
  return @pid
71
98
  end
72
-
99
+
73
100
  def resume(*arguments)
74
101
  @fiber.resume(*arguments)
75
102
  end
76
103
  end
77
104
 
78
105
  # Create a new process group. Can specify `limit:` which limits the maximum number of concurrent processes.
79
- def initialize(limit: nil)
106
+ def initialize(limit: nil, terminal: Terminal::Device.new?)
80
107
  raise ArgumentError.new("Limit must be nil (unlimited) or > 0") unless limit == nil or limit > 0
81
108
 
82
109
  @pid = Process.pid
83
110
 
111
+ @terminal = terminal
112
+
84
113
  @queue = []
85
114
  @limit = limit
86
-
115
+
87
116
  @running = {}
88
117
  @fiber = nil
89
-
118
+
90
119
  @pgid = nil
120
+
121
+ # Whether we can actively schedule tasks or not:
122
+ @waiting = false
91
123
  end
92
124
 
93
125
  # A table of currently running processes.
@@ -102,29 +134,39 @@ module Process
102
134
 
103
135
  -@pgid
104
136
  end
105
-
137
+
138
+ def queued?
139
+ @queue.size > 0
140
+ end
141
+
106
142
  # Are there processes currently running?
107
143
  def running?
108
144
  @running.size > 0
109
145
  end
110
-
146
+
111
147
  # Run a process in a new fiber, arguments have same meaning as Process#spawn.
112
- def run(*arguments)
148
+ def run(*arguments, **options)
113
149
  Fiber.new do
114
- exit_status = self.spawn(*arguments)
150
+ exit_status = self.spawn(*arguments, **options)
115
151
 
116
152
  yield exit_status if block_given?
117
153
  end.resume
118
154
  end
119
155
 
156
+ def async
157
+ Fiber.new do
158
+ yield self
159
+ end.resume
160
+ end
161
+
120
162
  # Run a specific command as a child process.
121
163
  def spawn(*arguments, **options)
122
- append! Command.new(arguments, options)
164
+ append! Spawn.new(arguments, **options)
123
165
  end
124
166
 
125
167
  # Fork a block as a child process.
126
168
  def fork(**options, &block)
127
- append! Fork.new(block, options)
169
+ append! Fork.new(block, **options)
128
170
  end
129
171
 
130
172
  # Whether or not #spawn, #fork or #run can be scheduled immediately.
@@ -141,20 +183,26 @@ module Process
141
183
  not available?
142
184
  end
143
185
 
144
- # Wait for all running and queued processes to finish.
186
+ # Wait for all running and queued processes to finish. If you provide a block, it will be invoked before waiting, but within canonical signal handling machinery.
145
187
  def wait
146
188
  raise ArgumentError.new("Cannot call Process::Group#wait from child process!") unless @pid == Process.pid
147
189
 
148
- while running?
149
- process, status = wait_one
150
-
151
- schedule!
190
+ waiting do
191
+ yield(self) if block_given?
152
192
 
153
- process.resume(status)
193
+ while running?
194
+ process, status = wait_one
195
+
196
+ schedule!
197
+
198
+ process.resume(status)
199
+ end
154
200
  end
155
201
 
156
202
  # No processes, process group is no longer valid:
157
203
  @pgid = nil
204
+
205
+ return self
158
206
  rescue Interrupt
159
207
  # If the user interrupts the wait, interrupt the process group and wait for them to finish:
160
208
  self.kill(:INT)
@@ -182,13 +230,33 @@ module Process
182
230
  end
183
231
  end
184
232
 
233
+ def to_s
234
+ "#<#{self.class} running=#{@running.size} queued=#{@queue.size} limit=#{@limit} pgid=#{@pgid}>"
235
+ end
236
+
185
237
  private
186
238
 
239
+ # The waiting loop, schedule any outstanding tasks:
240
+ def waiting
241
+ @waiting = true
242
+
243
+ # Schedule any queued tasks:
244
+ schedule!
245
+
246
+ yield
247
+ ensure
248
+ @waiting = false
249
+ end
250
+
251
+ def waiting?
252
+ @waiting
253
+ end
254
+
187
255
  # Append a process to the queue and schedule it for execution if possible.
188
256
  def append!(process)
189
257
  @queue << process
190
258
 
191
- schedule!
259
+ schedule! if waiting?
192
260
 
193
261
  Fiber.yield
194
262
  end
@@ -197,14 +265,18 @@ module Process
197
265
  def schedule!
198
266
  while available? and @queue.size > 0
199
267
  process = @queue.shift
200
-
268
+
201
269
  if @running.size == 0
202
- pid = process.run(:pgroup => true)
270
+ pid = process.call(:pgroup => true)
203
271
 
204
272
  # The process group id is the pid of the first process:
205
273
  @pgid = pid
206
274
  else
207
- pid = process.run(:pgroup => @pgid)
275
+ pid = process.call(:pgroup => @pgid)
276
+ end
277
+
278
+ if @terminal and process.foreground?
279
+ @terminal.foreground = pid
208
280
  end
209
281
 
210
282
  @running[pid] = process
@@ -214,6 +286,9 @@ module Process
214
286
  # Wait for all children to exit but without resuming any controlling fibers.
215
287
  def wait_all
216
288
  wait_one while running?
289
+
290
+ # Clear any queued tasks:
291
+ @queue.clear
217
292
  end
218
293
 
219
294
  # Wait for one process, should only be called when a child process has finished, otherwise would block.
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Process
22
22
  class Group
23
- VERSION = "1.0.1"
23
+ VERSION = "1.2.3"
24
24
  end
25
25
  end
metadata CHANGED
@@ -1,45 +1,73 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process-group
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-09 00:00:00.000000000 Z
11
+ date: 2020-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: process-terminal
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
20
- type: :development
19
+ version: 0.2.0
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.3'
26
+ version: 0.2.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: rspec
28
+ name: bake-bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 3.4.0
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bake-modernize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
39
67
  - !ruby/object:Gem::Version
40
- version: 3.4.0
68
+ version: '0'
41
69
  - !ruby/object:Gem::Dependency
42
- name: rake
70
+ name: covered
43
71
  requirement: !ruby/object:Gem::Requirement
44
72
  requirements:
45
73
  - - ">="
@@ -52,37 +80,34 @@ dependencies:
52
80
  - - ">="
53
81
  - !ruby/object:Gem::Version
54
82
  version: '0'
55
- description: "\tManages a unix process group for running multiple processes, keeps
56
- track of multiple processes and leverages fibers to provide predictable behaviour
57
- in complicated process-based scripts.\n"
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.9.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 3.9.0
97
+ description:
58
98
  email:
59
- - samuel.williams@oriontransfer.co.nz
60
99
  executables: []
61
100
  extensions: []
62
101
  extra_rdoc_files: []
63
102
  files:
64
- - ".gitignore"
65
- - ".rspec"
66
- - ".simplecov"
67
- - ".travis.yml"
68
- - Gemfile
69
- - README.md
70
- - Rakefile
71
- - examples/terminal.rb
72
103
  - lib/process/group.rb
73
104
  - lib/process/group/version.rb
74
- - process-group.gemspec
75
- - spec/process/group/fork_spec.rb
76
- - spec/process/group/interrupt_spec.rb
77
- - spec/process/group/io_spec.rb
78
- - spec/process/group/load_spec.rb
79
- - spec/process/group/process_spec.rb
80
- - spec/process/group/spawn_spec.rb
81
105
  homepage: https://github.com/ioquatix/process-group
82
106
  licenses:
83
107
  - MIT
84
- metadata: {}
85
- post_install_message:
108
+ metadata:
109
+ funding_uri: https://github.com/sponsors/ioquatix/
110
+ post_install_message:
86
111
  rdoc_options: []
87
112
  require_paths:
88
113
  - lib
@@ -97,15 +122,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
122
  - !ruby/object:Gem::Version
98
123
  version: '0'
99
124
  requirements: []
100
- rubyforge_project:
101
- rubygems_version: 2.4.6
102
- signing_key:
125
+ rubygems_version: 3.1.2
126
+ signing_key:
103
127
  specification_version: 4
104
- summary: Run processes concurrently in separate fibers with predictable behaviour.
105
- test_files:
106
- - spec/process/group/fork_spec.rb
107
- - spec/process/group/interrupt_spec.rb
108
- - spec/process/group/io_spec.rb
109
- - spec/process/group/load_spec.rb
110
- - spec/process/group/process_spec.rb
111
- - spec/process/group/spawn_spec.rb
128
+ summary: Run and manage multiple processes in separate fibers with predictable behaviour.
129
+ test_files: []
data/.gitignore DELETED
@@ -1,17 +0,0 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --color
2
- --format documentation
data/.simplecov DELETED
@@ -1,9 +0,0 @@
1
-
2
- SimpleCov.start do
3
- add_filter "/spec/"
4
- end
5
-
6
- if ENV['TRAVIS']
7
- require 'coveralls'
8
- Coveralls.wear!
9
- end
@@ -1,14 +0,0 @@
1
- language: ruby
2
- sudo: false
3
- rvm:
4
- - 2.0.0
5
- - 2.1.8
6
- - 2.2.4
7
- - 2.3.0
8
- - ruby-head
9
- - rbx-2
10
- env: COVERAGE=true
11
- matrix:
12
- allow_failures:
13
- - rvm: "rbx-2"
14
-
data/Gemfile DELETED
@@ -1,9 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in process-pool.gemspec
4
- gemspec
5
-
6
- group :test do
7
- gem 'simplecov'
8
- gem 'coveralls', require: false
9
- end
data/README.md DELETED
@@ -1,182 +0,0 @@
1
- # Process::Group
2
-
3
- `Process::Group` allows for multiple fibers to run system processes concurrently with minimal overhead.
4
-
5
- [![Build Status](https://secure.travis-ci.org/ioquatix/process-group.svg)](http://travis-ci.org/ioquatix/process-group)
6
- [![Code Climate](https://codeclimate.com/github/ioquatix/process-group.svg)](https://codeclimate.com/github/ioquatix/process-group)
7
- [![Coverage Status](https://coveralls.io/repos/ioquatix/process-group/badge.svg)](https://coveralls.io/r/ioquatix/process-group)
8
- [![Documentation](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/process-group)
9
- [![Code](http://img.shields.io/badge/github-code-blue.svg)](https://github.com/ioquatix/process-group)
10
-
11
- ## Installation
12
-
13
- Add this line to your application's Gemfile:
14
-
15
- gem 'process-group'
16
-
17
- And then execute:
18
-
19
- $ bundle
20
-
21
- Or install it yourself as:
22
-
23
- $ gem install process-group
24
-
25
- ## Usage
26
-
27
- The simplest concurrent usage is as follows:
28
-
29
- # Create a new process group:
30
- group = Process::Group.new
31
-
32
- # Run the command (non-blocking):
33
- group.run("sleep 1") do |exit_status|
34
- # Running in a separate fiber, will execute this code once the process completes:
35
- puts "Command finished with status: #{exit_status}"
36
- end
37
-
38
- # Do something else here:
39
- sleep(1)
40
-
41
- # Wait for all processes in group to finish:
42
- group.wait
43
-
44
- The `group.wait` call is an explicit synchronisation point, and if it completes successfully, all processes/fibers have finished successfully. If an error is raised in a fiber, it will be passed back out through `group.wait` and this is the only failure condition. Even if this occurs, all children processes are guaranteed to be cleaned up.
45
-
46
- ### Explicit Fibers
47
-
48
- Items within a single fiber will execute sequentially. Processes (e.g. via `Group#spawn`) will run concurrently in multiple fibers.
49
-
50
- group = Process::Group.new
51
-
52
- # Explicity manage concurrency in this fiber:
53
- Fiber.new do
54
- # These processes will be run sequentially:
55
- group.spawn("sleep 1")
56
- group.spawn("sleep 1")
57
- end.resume
58
-
59
- # Implicitly run this task concurrently as the above fiber:
60
- group.run("sleep 2")
61
-
62
- # Wait for fiber to complete:
63
- group.wait
64
-
65
- `Group#spawn` is theoretically identical to `Process#spawn` except the processes are run concurrently if possible.
66
-
67
- ### Specify Options
68
-
69
- You can specify options to `Group#run` and `Group#spawn` just like `Process::spawn`:
70
-
71
- group = Process::Group.new
72
-
73
- env = {'FOO' => 'BAR'}
74
-
75
- # Arguments are essentially the same as Process::spawn.
76
- group.run(env, "sleep 1", chdir: "/tmp")
77
-
78
- group.wait
79
-
80
- ### Process Limit
81
-
82
- The process group can be used as a way to spawn multiple processes, but sometimes you'd like to limit the number of parallel processes to something relating to the number of processors in the system. A number of options exist.
83
-
84
- # 'facter' gem - found a bit slow to initialise, but most widely supported.
85
- require 'facter'
86
- group = Process::Group.new(limit: Facter.processorcount)
87
-
88
- # 'system' gem - found very fast, less wide support (but nothing really important).
89
- require 'system'
90
- group = Process::Group.new(limit: System::CPU.count)
91
-
92
- # hardcoded - set to n (8 < n < 32) and let the OS scheduler worry about it.
93
- group = Process::Group.new(limit: 32)
94
-
95
- # unlimited - default.
96
- group = Process::Group.new
97
-
98
- ### Kill Group
99
-
100
- It is possible to send a signal (kill) to the entire process group:
101
-
102
- group.kill(:TERM)
103
-
104
- If there are no running processes, this is a no-op (rather than an error).
105
-
106
- #### Handling Interrupts
107
-
108
- `Process::Group` transparently handles `Interrupt` when raised within a `Fiber`. If `Interrupt` is raised, all children processes will be sent `kill(:INT)` and we will wait for all children to complete, but without resuming the controlling fibers. If `Interrupt` is raised during this process, children will be sent `kill(:TERM)`. After calling `Interrupt`, the fibers will not be resumed.
109
-
110
- ### Process Timeout
111
-
112
- You can run a process group with a time limit by using a separate child process:
113
-
114
- group = Process::Group.new
115
-
116
- class Timeout < StandardError
117
- end
118
-
119
- Fiber.new do
120
- # Wait for 2 seconds, let other processes run:
121
- group.fork { sleep 2 }
122
-
123
- # If no other processes are running, we are done:
124
- Fiber.yield unless group.running?
125
-
126
- # Send SIGINT to currently running processes:
127
- group.kill(:INT)
128
-
129
- # Wait for 2 seconds, let other processes run:
130
- group.fork { sleep 2 }
131
-
132
- # If no other processes are running, we are done:
133
- Fiber.yield unless group.running?
134
-
135
- # Send SIGTERM to currently running processes:
136
- group.kill(:TERM)
137
-
138
- # Raise an Timeout exception which is based back out:
139
- raise Timeout
140
- end.resume
141
-
142
- # Run some other long task:
143
- group.run("sleep 10")
144
-
145
- # Wait for fiber to complete:
146
- begin
147
- group.wait
148
- rescue Timeout
149
- puts "Process group was terminated forcefully."
150
- end
151
-
152
- ## Contributing
153
-
154
- 1. Fork it
155
- 2. Create your feature branch (`git checkout -b my-new-feature`)
156
- 3. Commit your changes (`git commit -am 'Add some feature'`)
157
- 4. Push to the branch (`git push origin my-new-feature`)
158
- 5. Create new Pull Request
159
-
160
- ## License
161
-
162
- Released under the MIT license.
163
-
164
- Copyright, 2014, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
165
-
166
- Permission is hereby granted, free of charge, to any person obtaining a copy
167
- of this software and associated documentation files (the "Software"), to deal
168
- in the Software without restriction, including without limitation the rights
169
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
170
- copies of the Software, and to permit persons to whom the Software is
171
- furnished to do so, subject to the following conditions:
172
-
173
- The above copyright notice and this permission notice shall be included in
174
- all copies or substantial portions of the Software.
175
-
176
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
177
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
178
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
179
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
180
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
181
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
182
- THE SOFTWARE.
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec) do |task|
5
- task.rspec_opts = ["--require", "simplecov"] if ENV['COVERAGE']
6
- end
7
-
8
- task :default => :spec
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Copyright, 2014, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
-
23
- require_relative '../lib/process/group'
24
-
25
- group = Process::Group.new
26
-
27
- 5.times do
28
- Fiber.new do
29
- result = group.fork do
30
- begin
31
- sleep 1 while true
32
- rescue Interrupt
33
- puts "Interrupted in child #{Process.pid}"
34
- end
35
- end
36
- end.resume
37
- end
38
-
39
- begin
40
- group.wait
41
- rescue Interrupt
42
- puts "Interrupted in parent #{Process.pid}"
43
- end
@@ -1,28 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'process/group/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "process-group"
8
- spec.version = Process::Group::VERSION
9
- spec.authors = ["Samuel Williams"]
10
- spec.email = ["samuel.williams@oriontransfer.co.nz"]
11
- spec.description = <<-EOF
12
- Manages a unix process group for running multiple processes, keeps track of multiple processes and leverages fibers to provide predictable behaviour in complicated process-based scripts.
13
- EOF
14
- spec.summary = %q{Run processes concurrently in separate fibers with predictable behaviour.}
15
- spec.homepage = "https://github.com/ioquatix/process-group"
16
- spec.license = "MIT"
17
-
18
- spec.files = `git ls-files`.split($/)
19
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
- spec.require_paths = ["lib"]
22
-
23
- spec.required_ruby_version = '>= 2.0'
24
-
25
- spec.add_development_dependency "bundler", "~> 1.3"
26
- spec.add_development_dependency "rspec", "~> 3.4.0"
27
- spec.add_development_dependency "rake"
28
- end
@@ -1,65 +0,0 @@
1
- # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require 'process/group'
22
-
23
- module Process::Group::ForkSpec
24
- describe Process::Group do
25
- it "should fork and write to pipe" do
26
- group = Process::Group.new
27
-
28
- input, output = IO.pipe
29
-
30
- Fiber.new do
31
- result = group.fork do
32
- output.puts "Hello World"
33
-
34
- exit(1)
35
- end
36
-
37
- expect(result.exitstatus).to be == 1
38
- end.resume
39
-
40
- output.close
41
-
42
- group.wait
43
-
44
- expect(input.read).to be == "Hello World\n"
45
- end
46
-
47
- it "should not throw interrupt from fork" do
48
- group = Process::Group.new
49
-
50
- Fiber.new do
51
- result = group.fork do
52
- # Don't print out a backtrace when Ruby invariably exits due to the execption below:
53
- $stderr.reopen('/dev/null', 'w')
54
-
55
- raise Interrupt
56
- end
57
-
58
- expect(result.exitstatus).not_to be == 0
59
- end.resume
60
-
61
- # Shouldn't raise any errors:
62
- group.wait
63
- end
64
- end
65
- end
@@ -1,150 +0,0 @@
1
- # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require 'process/group'
22
-
23
- module Process::Group::InterruptSpec
24
- describe Process::Group do
25
- it "should raise interrupt exception" do
26
- group = Process::Group.new
27
- checkpoint = ""
28
-
29
- Fiber.new do
30
- checkpoint += 'X'
31
-
32
- result = group.fork { sleep 0.1 }
33
-
34
- expect(result).to be == 0
35
-
36
- checkpoint += 'Y'
37
-
38
- # Simulate the user pressing Ctrl-C after 0.5 seconds:
39
- raise Interrupt
40
- end.resume
41
-
42
- Fiber.new do
43
- checkpoint += 'A'
44
-
45
- # This never returns:
46
- result = group.fork { sleep 0.2 }
47
-
48
- checkpoint += 'B'
49
- end.resume
50
-
51
- expect(group).to receive(:kill).with(:INT).once
52
- expect(group).to receive(:kill).with(:TERM).once
53
-
54
- expect do
55
- group.wait
56
- end.to raise_error(Interrupt)
57
-
58
- expect(checkpoint).to be == 'XAY'
59
- end
60
-
61
- it "should raise an exception" do
62
- group = Process::Group.new
63
- checkpoint = ""
64
-
65
- Fiber.new do
66
- checkpoint += 'X'
67
-
68
- result = group.fork { sleep 0.1 }
69
- expect(result).to be == 0
70
-
71
- checkpoint += 'Y'
72
-
73
- # Raises a RuntimeError
74
- fail "Error"
75
- end.resume
76
-
77
- Fiber.new do
78
- checkpoint += 'A'
79
-
80
- # This never returns:
81
- result = group.fork { sleep 0.2 }
82
-
83
- checkpoint += 'B'
84
- end.resume
85
-
86
- expect do
87
- expect(group).to receive(:kill).with(:TERM).once
88
-
89
- group.wait
90
- end.to raise_error(RuntimeError)
91
-
92
- expect(checkpoint).to be == 'XAY'
93
- end
94
-
95
- class Timeout < StandardError
96
- end
97
-
98
- it "should pass back out exceptions" do
99
- group = Process::Group.new
100
- checkpoint = ""
101
-
102
- Fiber.new do
103
- # Wait for 2 seconds, let other processes run:
104
- group.fork { sleep 2 }
105
- checkpoint += 'A'
106
- #puts "Finished waiting #1..."
107
-
108
- # If no other processes are running, we are done:
109
- Fiber.yield unless group.running?
110
- checkpoint += 'B'
111
- #puts "Sending SIGINT..."
112
-
113
- # Send SIGINT to currently running processes:
114
- group.kill(:INT)
115
-
116
- # Wait for 2 seconds, let other processes run:
117
- group.fork { sleep 2 }
118
- checkpoint += 'C'
119
- #puts "Finished waiting #2..."
120
-
121
- # If no other processes are running, we are done:
122
- Fiber.yield unless group.running?
123
- checkpoint += 'D'
124
- #puts "Sending SIGTERM..."
125
-
126
- # Send SIGTERM to currently running processes:
127
- group.kill(:TERM)
128
-
129
- # Raise an Timeout exception which is pased back out:
130
- raise Timeout
131
- end.resume
132
-
133
- # Run some other long task:
134
- group.run("sleep 10")
135
-
136
- start_time = Time.now
137
-
138
- # Wait for fiber to complete:
139
- expect do
140
- group.wait
141
- checkpoint += 'E'
142
- end.not_to raise_error
143
-
144
- end_time = Time.now
145
-
146
- expect(checkpoint).to be == 'ABCE'
147
- expect(end_time - start_time).to be_within(0.2).of 4.0
148
- end
149
- end
150
- end
@@ -1,56 +0,0 @@
1
- # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require 'process/group'
22
-
23
- module Process::Group::IOSpec
24
- describe Process::Group do
25
- it "should read line on separate thread" do
26
- group = Process::Group.new
27
-
28
- input, output = IO.pipe
29
-
30
- Fiber.new do
31
- result = group.fork do
32
- 3.times do
33
- output.puts "Hello World"
34
- sleep 0.1
35
- end
36
-
37
- exit(0)
38
- end
39
-
40
- expect(result).to be == 0
41
- end.resume
42
-
43
- output.close
44
-
45
- lines = nil
46
- io_thread = Thread.new do
47
- lines = input.read
48
- end
49
-
50
- group.wait
51
-
52
- io_thread.join
53
- expect(lines).to be == ("Hello World\n" * 3)
54
- end
55
- end
56
- end
@@ -1,46 +0,0 @@
1
- # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require 'process/group'
22
-
23
- module Process::Group::LoadSpec
24
- describe Process::Group do
25
- it "should only run a limited number of processes" do
26
- group = Process::Group.new(limit: 5)
27
-
28
- expect(group.available?).to be_truthy
29
- expect(group.blocking?).to be_falsey
30
-
31
- 5.times do
32
- Fiber.new do
33
- result = group.fork do
34
- exit(0)
35
- end
36
-
37
- expect(result.exitstatus).to be == 0
38
- end.resume
39
- end
40
-
41
- expect(group.blocking?).to be_truthy
42
-
43
- group.wait
44
- end
45
- end
46
- end
@@ -1,57 +0,0 @@
1
- # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- module Process::Group::ProcessSpec
22
- describe Process do
23
- it "default fork exit status should be 0" do
24
- pid = fork do
25
- end
26
-
27
- Process.waitpid(pid)
28
-
29
- expect($?.exitstatus).to be == 0
30
- end
31
-
32
- it "should fork and return exit status correctly" do
33
- pid = fork do
34
- exit(1)
35
- end
36
-
37
- Process.waitpid(pid)
38
-
39
- expect($?.exitstatus).to be == 1
40
- end
41
-
42
- # This is currently broken on Rubinius.
43
- it "should be okay to use fork within a fiber" do
44
- pid = nil
45
-
46
- Fiber.new do
47
- pid = fork do
48
- exit(2)
49
- end
50
- end.resume
51
-
52
- Process.waitpid(pid)
53
-
54
- expect($?.exitstatus).to be == 2
55
- end
56
- end
57
- end
@@ -1,109 +0,0 @@
1
- # Copyright, 2012, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
20
-
21
- require 'process/group'
22
-
23
- module Process::Group::SpawnSpec
24
- describe Process::Group do
25
- it "should execute fibers concurrently" do
26
- group = Process::Group.new
27
-
28
- start_time = Time.now
29
-
30
- Fiber.new do
31
- result = group.fork { sleep 1.0 }
32
-
33
- expect(result).to be == 0
34
- end.resume
35
-
36
- Fiber.new do
37
- result = group.fork { sleep 2.0 }
38
-
39
- expect(result).to be == 0
40
- end.resume
41
-
42
- group.wait
43
-
44
- end_time = Time.now
45
-
46
- # Check that the execution time was roughly 2 seconds:
47
- expect(end_time - start_time).to be_within(0.1).of(2.0)
48
- end
49
-
50
- it "should kill commands" do
51
- group = Process::Group.new
52
-
53
- start_time = Time.now
54
-
55
- group.run("sleep 1") do |exit_status|
56
- expect(exit_status).to_not be 0
57
- end
58
-
59
- group.run("sleep 2") do |exit_status|
60
- expect(exit_status).to_not be 0
61
- end
62
-
63
- group.kill(:KILL)
64
-
65
- group.wait
66
-
67
- end_time = Time.now
68
-
69
- # Check that processes killed almost immediately:
70
- expect(end_time - start_time).to be < 0.2
71
- end
72
-
73
- it "should pass environment to child process" do
74
- group = Process::Group.new
75
-
76
- env = {'FOO' => 'BAR'}
77
-
78
- # Make a pipe to receive output from child process:
79
- input, output = IO.pipe
80
-
81
- group.run(env, "echo $FOO", out: output) do |exit_status|
82
- output.close
83
- end
84
-
85
- group.wait
86
-
87
- expect(input.read).to be == "BAR\n"
88
- end
89
-
90
- it "should yield exit status" do
91
- group = Process::Group.new
92
-
93
- start_time = Time.now
94
-
95
- group.run("sleep 1")
96
-
97
- group.run("sleep 1") do |exit_status|
98
- expect(exit_status).to be == 0
99
- end
100
-
101
- group.wait
102
-
103
- end_time = Time.now
104
-
105
- # Check that the execution time was roughly 1 second:
106
- expect(end_time - start_time).to be_within(0.1).of(1.0)
107
- end
108
- end
109
- end