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 +5 -5
- data/lib/process/group.rb +117 -42
- data/lib/process/group/version.rb +1 -1
- metadata +63 -45
- data/.gitignore +0 -17
- data/.rspec +0 -2
- data/.simplecov +0 -9
- data/.travis.yml +0 -14
- data/Gemfile +0 -9
- data/README.md +0 -182
- data/Rakefile +0 -8
- data/examples/terminal.rb +0 -43
- data/process-group.gemspec +0 -28
- data/spec/process/group/fork_spec.rb +0 -65
- data/spec/process/group/interrupt_spec.rb +0 -150
- data/spec/process/group/io_spec.rb +0 -56
- data/spec/process/group/load_spec.rb +0 -46
- data/spec/process/group/process_spec.rb +0 -57
- data/spec/process/group/spawn_spec.rb +0 -109
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a2ef7e85dc264aa4d6254170467cd4fc1e47d2c5f552b3c55528370142526452
|
4
|
+
data.tar.gz: 7e8d6ccb53689d31a427484cfab31bb783a6896de2208c2780f664f3b6e5e883
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2106bb6e1e7f2f7f20a63781d82b95786305b53005bdff44cd0bdc5e1a2ff13c6efefc26c715f3d76919b683309afac6016e18dbdbc374afb736a36c9b1c4a1c
|
7
|
+
data.tar.gz: 8e96440ab717809e09c06f11ea3e9bb8596f31613804b04a11a84266a55008fe22d42a47129798062cad0ebc9dd54dc3ea0652664a36b535a0cc3ae4d4f1c1af
|
data/lib/process/group.rb
CHANGED
@@ -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
|
-
|
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(
|
29
|
-
@arguments = arguments
|
34
|
+
def initialize(foreground: false, **options)
|
30
35
|
@options = options
|
31
|
-
|
32
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
#
|
50
|
-
class
|
51
|
-
def initialize(
|
52
|
-
@
|
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
|
-
|
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
|
-
|
81
|
+
super(**options)
|
57
82
|
end
|
58
83
|
|
59
|
-
def
|
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
|
-
|
92
|
+
elsif pgroup = options[:pgroup]
|
66
93
|
# Set this process as part of the existing process group:
|
67
|
-
Process.setpgid(@pid,
|
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!
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
schedule!
|
190
|
+
waiting do
|
191
|
+
yield(self) if block_given?
|
152
192
|
|
153
|
-
|
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.
|
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.
|
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.
|
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.
|
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:
|
11
|
+
date: 2020-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: process-terminal
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
type: :
|
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:
|
26
|
+
version: 0.2.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: bake-bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
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:
|
68
|
+
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
signing_key:
|
125
|
+
rubygems_version: 3.1.2
|
126
|
+
signing_key:
|
103
127
|
specification_version: 4
|
104
|
-
summary: Run processes
|
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
data/.rspec
DELETED
data/.simplecov
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
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
|
-
[](http://travis-ci.org/ioquatix/process-group)
|
6
|
-
[](https://codeclimate.com/github/ioquatix/process-group)
|
7
|
-
[](https://coveralls.io/r/ioquatix/process-group)
|
8
|
-
[](http://www.rubydoc.info/gems/process-group)
|
9
|
-
[](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
data/examples/terminal.rb
DELETED
@@ -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
|
data/process-group.gemspec
DELETED
@@ -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
|