process-group 1.0.1 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
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