async-container 0.14.1 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +0 -1
- data/async-container.gemspec +1 -1
- data/examples/container.rb +24 -13
- data/examples/threads.rb +24 -0
- data/lib/async/container.rb +1 -23
- data/lib/async/container/best.rb +44 -0
- data/lib/async/container/controller.rb +79 -27
- data/lib/async/container/{terminator.rb → error.rb} +1 -8
- data/lib/async/container/forked.rb +7 -12
- data/lib/async/container/generic.rb +64 -0
- data/lib/async/container/group.rb +40 -19
- data/lib/async/container/statistics.rb +10 -0
- data/lib/async/container/threaded.rb +7 -11
- data/lib/async/container/version.rb +1 -1
- data/spec/async/container/controller_spec.rb +53 -0
- data/spec/async/container/forked_spec.rb +2 -1
- data/spec/async/container/hybrid_spec.rb +1 -1
- data/spec/async/container/shared_examples.rb +17 -12
- metadata +11 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f6640daaa86e57d05f5c7ec2a0d25d98b355edb0926d45ba1e2414adfdaa46e
|
4
|
+
data.tar.gz: fd25c58be73c64bf64dec42ac24e84b9f5c66fdb26da9780246c877bd38056f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6011d00efba48db1d17fedf7fe9823210c58840611a140710be8ce88a55e39b5421bebf666d97a4741f5c97c1f16758f6bc7598fd4205ffb0b2c975173e496f
|
7
|
+
data.tar.gz: d0c020e874296fc31abdd6638d7113ddb0ff7e718e142c9c8c77b40ad7874e848a78b9e351e45f9439421d1629d58cb20fe086d3966d07345c3cb46fe9a517a3
|
data/.travis.yml
CHANGED
data/async-container.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_runtime_dependency "process-group"
|
24
24
|
|
25
25
|
spec.add_runtime_dependency "async", "~> 1.0"
|
26
|
-
spec.add_runtime_dependency "async-io", "~> 1.
|
26
|
+
spec.add_runtime_dependency "async-io", "~> 1.26"
|
27
27
|
|
28
28
|
spec.add_development_dependency "async-rspec", "~> 1.1"
|
29
29
|
|
data/examples/container.rb
CHANGED
@@ -1,21 +1,32 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'async/container/
|
3
|
+
require '../lib/async/container/controller'
|
4
|
+
require '../lib/async/container/forked'
|
4
5
|
|
5
|
-
|
6
|
+
Async.logger.debug!
|
6
7
|
|
7
|
-
|
8
|
+
Async.logger.debug(self, "Starting up...")
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
controller = Async::Container::Controller.new do |container|
|
11
|
+
Async.logger.debug(self, "Setting up container...")
|
12
|
+
|
13
|
+
container.run(count: 1, restart: true) do
|
14
|
+
Async.logger.debug(self, "Child process started.")
|
15
|
+
|
16
|
+
while true
|
17
|
+
sleep 1
|
18
|
+
|
19
|
+
if rand < 0.1
|
20
|
+
exit(1)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
ensure
|
24
|
+
Async.logger.debug(self, "Child process exiting:", $!)
|
14
25
|
end
|
15
|
-
ensure
|
16
|
-
puts "Exiting: #{$!}"
|
17
26
|
end
|
18
27
|
|
19
|
-
|
20
|
-
|
21
|
-
|
28
|
+
begin
|
29
|
+
controller.run
|
30
|
+
ensure
|
31
|
+
Async.logger.debug(controller, "Parent process exiting:", $!)
|
32
|
+
end
|
data/examples/threads.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
puts "Process pid: #{Process.pid}"
|
4
|
+
|
5
|
+
threads = 10.times.collect do
|
6
|
+
Thread.new do
|
7
|
+
begin
|
8
|
+
sleep
|
9
|
+
rescue Exception
|
10
|
+
puts "Thread: #{$!}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
while true
|
16
|
+
begin
|
17
|
+
threads.each(&:join)
|
18
|
+
exit(0)
|
19
|
+
rescue Exception
|
20
|
+
puts "Join: #{$!}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
puts "Done"
|
data/lib/async/container.rb
CHANGED
@@ -18,32 +18,10 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require_relative 'container/
|
22
|
-
require_relative 'container/threaded'
|
23
|
-
require_relative 'container/hybrid'
|
24
|
-
|
25
|
-
require 'etc'
|
21
|
+
require_relative 'container/controller'
|
26
22
|
|
27
23
|
module Async
|
28
24
|
# Containers execute one or more "instances" which typically contain a reactor. A container spawns "instances" using threads and/or processes. Because these are resources that must be cleaned up some how (either by `join` or `waitpid`), their creation is deferred until the user invokes `Container#wait`. When executed this way, the container guarantees that all "instances" will be complete once `Container#wait` returns. Containers are constructs for achieving parallelism, and are not designed to be used directly for concurrency. Typically, you'd create one or more container, add some tasks to it, and then wait for it to complete.
|
29
25
|
module Container
|
30
|
-
def self.best_container_class
|
31
|
-
if Process.respond_to?(:fork) and Process.respond_to?(:setpgid)
|
32
|
-
return Forked
|
33
|
-
else
|
34
|
-
return Threaded
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [Integer] the number of hardware processors which can run threads/processes simultaneously.
|
39
|
-
def self.processor_count
|
40
|
-
Etc.nprocessors
|
41
|
-
rescue
|
42
|
-
2
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.new(*arguments)
|
46
|
-
best_container_class.new(*arguments)
|
47
|
-
end
|
48
26
|
end
|
49
27
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright, 2019, 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_relative 'forked'
|
22
|
+
require_relative 'threaded'
|
23
|
+
require_relative 'hybrid'
|
24
|
+
|
25
|
+
module Async
|
26
|
+
# Containers execute one or more "instances" which typically contain a reactor. A container spawns "instances" using threads and/or processes. Because these are resources that must be cleaned up some how (either by `join` or `waitpid`), their creation is deferred until the user invokes `Container#wait`. When executed this way, the container guarantees that all "instances" will be complete once `Container#wait` returns. Containers are constructs for achieving parallelism, and are not designed to be used directly for concurrency. Typically, you'd create one or more container, add some tasks to it, and then wait for it to complete.
|
27
|
+
module Container
|
28
|
+
def self.fork?
|
29
|
+
Process.respond_to?(:fork) and Process.respond_to?(:setpgid)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.best_container_class
|
33
|
+
if fork?
|
34
|
+
return Forked
|
35
|
+
else
|
36
|
+
return Threaded
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.new(*arguments)
|
41
|
+
best_container_class.new(*arguments)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -18,49 +18,101 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require_relative '
|
21
|
+
require_relative 'error'
|
22
|
+
require_relative 'best'
|
22
23
|
|
23
|
-
|
24
|
+
require_relative 'statistics'
|
24
25
|
|
25
26
|
module Async
|
26
27
|
module Container
|
28
|
+
class ContainerFailed < Error
|
29
|
+
def initialize(container)
|
30
|
+
super("Could not create container!")
|
31
|
+
@container = container
|
32
|
+
end
|
33
|
+
|
34
|
+
attr :container
|
35
|
+
end
|
36
|
+
|
37
|
+
# Manages the life-cycle of a container.
|
27
38
|
class Controller
|
28
|
-
|
29
|
-
|
39
|
+
SIGHUP = Signal.list["HUP"]
|
40
|
+
DEFAULT_TIMEOUT = 2
|
41
|
+
|
42
|
+
def initialize(startup_duration: DEFAULT_TIMEOUT)
|
43
|
+
@container = nil
|
44
|
+
|
45
|
+
@startup_duration = startup_duration
|
46
|
+
end
|
47
|
+
|
48
|
+
attr :container
|
49
|
+
|
50
|
+
def create_container
|
51
|
+
Container.new
|
52
|
+
end
|
53
|
+
|
54
|
+
def setup(container)
|
55
|
+
end
|
56
|
+
|
57
|
+
def start
|
58
|
+
self.restart
|
59
|
+
end
|
60
|
+
|
61
|
+
def stop(graceful = true)
|
62
|
+
@container&.stop(graceful)
|
63
|
+
@container = nil
|
30
64
|
end
|
31
65
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
66
|
+
def restart(duration = @startup_duration)
|
67
|
+
hup_action = Signal.trap(:HUP, :IGNORE)
|
68
|
+
container = self.create_container
|
69
|
+
|
70
|
+
begin
|
71
|
+
self.setup(container)
|
72
|
+
rescue
|
73
|
+
raise ContainerFailed, container
|
35
74
|
end
|
36
75
|
|
37
|
-
|
38
|
-
|
76
|
+
Async.logger.debug(self, "Waiting for startup...")
|
77
|
+
container.sleep(duration)
|
78
|
+
Async.logger.debug(self, "Finished startup.")
|
79
|
+
|
80
|
+
if container.failed?
|
81
|
+
container.stop
|
82
|
+
|
83
|
+
raise ContainerFailed, container
|
39
84
|
end
|
40
85
|
|
41
|
-
|
86
|
+
@container&.stop
|
87
|
+
@container = container
|
88
|
+
ensure
|
89
|
+
Signal.trap(:HUP, hup_action)
|
42
90
|
end
|
43
91
|
|
44
|
-
def
|
45
|
-
|
92
|
+
def run
|
93
|
+
Async.logger.debug(self) {"Starting container..."}
|
94
|
+
|
95
|
+
self.start
|
96
|
+
|
97
|
+
while true
|
46
98
|
begin
|
47
|
-
|
48
|
-
rescue
|
49
|
-
|
99
|
+
@container.wait
|
100
|
+
rescue SignalException => exception
|
101
|
+
if exception.signo == SIGHUP
|
102
|
+
Async.logger.info(self) {"Reloading container..."}
|
103
|
+
|
104
|
+
begin
|
105
|
+
self.restart
|
106
|
+
rescue ContainerFailed => failure
|
107
|
+
Async.logger.error(self) {failure}
|
108
|
+
end
|
109
|
+
else
|
110
|
+
raise
|
111
|
+
end
|
50
112
|
end
|
51
113
|
end
|
52
|
-
|
53
|
-
|
54
|
-
def run(count: Container.processor_count, **options, &block)
|
55
|
-
count.times do
|
56
|
-
async(**options, &block)
|
57
|
-
end
|
58
|
-
|
59
|
-
return self
|
60
|
-
end
|
61
|
-
|
62
|
-
def stop(graceful = true)
|
63
|
-
@attached.each(&:stop)
|
114
|
+
ensure
|
115
|
+
self.stop
|
64
116
|
end
|
65
117
|
end
|
66
118
|
end
|
@@ -21,13 +21,13 @@
|
|
21
21
|
require 'async/reactor'
|
22
22
|
|
23
23
|
require_relative 'group'
|
24
|
-
require_relative '
|
24
|
+
require_relative 'generic'
|
25
25
|
require_relative 'statistics'
|
26
26
|
|
27
27
|
module Async
|
28
28
|
# Manages a reactor within one or more threads.
|
29
29
|
module Container
|
30
|
-
class Forked <
|
30
|
+
class Forked < Generic
|
31
31
|
UNNAMED = "Unnamed"
|
32
32
|
|
33
33
|
class Instance
|
@@ -52,11 +52,8 @@ module Async
|
|
52
52
|
super
|
53
53
|
|
54
54
|
@group = Group.new
|
55
|
-
@statistics = Statistics.new
|
56
55
|
end
|
57
56
|
|
58
|
-
attr :statistics
|
59
|
-
|
60
57
|
def spawn(name: nil, restart: false)
|
61
58
|
Fiber.new do
|
62
59
|
while true
|
@@ -85,18 +82,16 @@ module Async
|
|
85
82
|
return self
|
86
83
|
end
|
87
84
|
|
88
|
-
def
|
85
|
+
def sleep(duration)
|
86
|
+
@group.sleep(duration)
|
87
|
+
end
|
88
|
+
|
89
|
+
def wait
|
89
90
|
@group.wait
|
90
|
-
|
91
|
-
sleep if forever
|
92
|
-
rescue Interrupt
|
93
|
-
# Graceful exit.
|
94
91
|
end
|
95
92
|
|
96
93
|
# Gracefully shut down all children processes.
|
97
94
|
def stop(graceful = true)
|
98
|
-
super
|
99
|
-
|
100
95
|
@group.stop(graceful)
|
101
96
|
end
|
102
97
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright, 2018, 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 'async/reactor'
|
22
|
+
|
23
|
+
require 'etc'
|
24
|
+
|
25
|
+
module Async
|
26
|
+
module Container
|
27
|
+
# @return [Integer] the number of hardware processors which can run threads/processes simultaneously.
|
28
|
+
def self.processor_count
|
29
|
+
Etc.nprocessors
|
30
|
+
rescue
|
31
|
+
2
|
32
|
+
end
|
33
|
+
|
34
|
+
class Generic
|
35
|
+
def initialize
|
36
|
+
@statistics = Statistics.new
|
37
|
+
end
|
38
|
+
|
39
|
+
attr :statistics
|
40
|
+
|
41
|
+
def failed?
|
42
|
+
@statistics.failed?
|
43
|
+
end
|
44
|
+
|
45
|
+
def async(**options, &block)
|
46
|
+
spawn(**options) do |instance|
|
47
|
+
begin
|
48
|
+
Async::Reactor.run(instance, &block)
|
49
|
+
rescue Interrupt
|
50
|
+
# Graceful exit.
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def run(count: Container.processor_count, **options, &block)
|
56
|
+
count.times do
|
57
|
+
async(**options, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
return self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -18,27 +18,27 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
require 'async/reactor'
|
22
|
-
|
23
|
-
require_relative 'controller'
|
24
|
-
require_relative 'statistics'
|
25
|
-
|
26
21
|
module Async
|
27
|
-
# Manages a reactor within one or more threads.
|
28
22
|
module Container
|
29
23
|
class Group
|
30
24
|
def initialize
|
31
25
|
@pgid = nil
|
32
26
|
@running = {}
|
27
|
+
|
28
|
+
@queue = nil
|
33
29
|
end
|
34
30
|
|
35
31
|
def spawn(*arguments)
|
32
|
+
self.yield
|
33
|
+
|
36
34
|
if pid = ::Process.spawn(*arguments)
|
37
35
|
wait_for(pid)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
39
|
def fork(&block)
|
40
|
+
self.yield
|
41
|
+
|
42
42
|
if pid = ::Process.fork(&block)
|
43
43
|
wait_for(pid)
|
44
44
|
end
|
@@ -48,20 +48,22 @@ module Async
|
|
48
48
|
@running.any?
|
49
49
|
end
|
50
50
|
|
51
|
+
def sleep(duration)
|
52
|
+
self.resume
|
53
|
+
self.suspend
|
54
|
+
|
55
|
+
Kernel::sleep(duration)
|
56
|
+
|
57
|
+
while self.wait_one(::Process::WNOHANG)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
51
61
|
def wait
|
62
|
+
self.resume
|
63
|
+
|
52
64
|
while self.any?
|
53
65
|
self.wait_one
|
54
66
|
end
|
55
|
-
rescue Interrupt
|
56
|
-
# If the user interrupts the wait, interrupt the process group and wait for them to finish:
|
57
|
-
self.kill(:INT)
|
58
|
-
|
59
|
-
# If user presses Ctrl-C again (or something else goes wrong), we will come out and kill(:TERM) in the ensure below:
|
60
|
-
wait_all
|
61
|
-
|
62
|
-
raise
|
63
|
-
ensure
|
64
|
-
self.close
|
65
67
|
end
|
66
68
|
|
67
69
|
def kill(signal = :INT)
|
@@ -71,7 +73,7 @@ module Async
|
|
71
73
|
def stop(graceful = false)
|
72
74
|
if graceful
|
73
75
|
self.kill(:INT)
|
74
|
-
|
76
|
+
interrupt_all
|
75
77
|
end
|
76
78
|
ensure
|
77
79
|
self.close
|
@@ -85,15 +87,34 @@ module Async
|
|
85
87
|
end
|
86
88
|
|
87
89
|
# Clean up zombie processes - if user presses Ctrl-C or for some reason something else blows up, exception would propagate back to caller:
|
88
|
-
|
90
|
+
interrupt_all
|
89
91
|
end
|
90
92
|
|
91
93
|
protected
|
92
94
|
|
93
|
-
def
|
95
|
+
def yield
|
96
|
+
if @queue
|
97
|
+
@queue << Fiber.current
|
98
|
+
Fiber.yield
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def suspend
|
103
|
+
@queue ||= []
|
104
|
+
end
|
105
|
+
|
106
|
+
def resume
|
107
|
+
if @queue
|
108
|
+
@queue.each(&:resume)
|
109
|
+
@queue = nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def interrupt_all
|
94
114
|
while self.any?
|
95
115
|
self.wait_one do |fiber, status|
|
96
116
|
begin
|
117
|
+
# This causes the waiting fiber to `raise Interrupt`:
|
97
118
|
fiber.resume(nil)
|
98
119
|
rescue Interrupt
|
99
120
|
# Graceful exit.
|
@@ -21,13 +21,13 @@
|
|
21
21
|
require 'async/reactor'
|
22
22
|
require 'thread'
|
23
23
|
|
24
|
-
require_relative '
|
24
|
+
require_relative 'generic'
|
25
25
|
require_relative 'statistics'
|
26
26
|
|
27
27
|
module Async
|
28
28
|
module Container
|
29
29
|
# Manages a reactor within one or more threads.
|
30
|
-
class Threaded <
|
30
|
+
class Threaded < Generic
|
31
31
|
class Instance
|
32
32
|
def initialize(thread)
|
33
33
|
@thread = thread
|
@@ -59,11 +59,8 @@ module Async
|
|
59
59
|
|
60
60
|
@threads = []
|
61
61
|
@running = true
|
62
|
-
@statistics = Statistics.new
|
63
62
|
end
|
64
63
|
|
65
|
-
attr :statistics
|
66
|
-
|
67
64
|
def spawn(name: nil, restart: false, &block)
|
68
65
|
@statistics.spawn!
|
69
66
|
|
@@ -98,19 +95,18 @@ module Async
|
|
98
95
|
return self
|
99
96
|
end
|
100
97
|
|
101
|
-
def
|
98
|
+
def sleep(duration)
|
99
|
+
Kernel::sleep(duration)
|
100
|
+
end
|
101
|
+
|
102
|
+
def wait
|
102
103
|
@threads.each(&:join)
|
103
104
|
@threads.clear
|
104
|
-
|
105
|
-
sleep if forever
|
106
|
-
rescue Interrupt
|
107
|
-
# Graceful exit.
|
108
105
|
end
|
109
106
|
|
110
107
|
# Gracefully shut down all reactors.
|
111
108
|
def stop(graceful = true)
|
112
109
|
@running = false
|
113
|
-
super
|
114
110
|
|
115
111
|
if graceful
|
116
112
|
@threads.each{|thread| thread.raise(Interrupt)}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# Copyright, 2019, 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 "async/container/controller"
|
22
|
+
|
23
|
+
RSpec.describe Async::Container::Controller do
|
24
|
+
describe '#start' do
|
25
|
+
it "can start up a container" do
|
26
|
+
expect(subject).to receive(:setup)
|
27
|
+
|
28
|
+
subject.start
|
29
|
+
|
30
|
+
expect(subject.container).to_not be_nil
|
31
|
+
|
32
|
+
subject.stop
|
33
|
+
|
34
|
+
expect(subject.container).to be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can spawn a reactor" do
|
38
|
+
def subject.setup(container)
|
39
|
+
container.async do |task|
|
40
|
+
task.sleep 1
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
subject.start
|
45
|
+
|
46
|
+
statistics = subject.container.statistics
|
47
|
+
|
48
|
+
expect(statistics.spawns).to be == 1
|
49
|
+
|
50
|
+
subject.stop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -18,11 +18,12 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require "async/container"
|
21
22
|
require "async/container/forked"
|
22
23
|
|
23
24
|
require_relative 'shared_examples'
|
24
25
|
|
25
|
-
RSpec.describe Async::Container::Forked do
|
26
|
+
RSpec.describe Async::Container::Forked, if: Async::Container.fork? do
|
26
27
|
it_behaves_like Async::Container
|
27
28
|
|
28
29
|
it "can restart child" do
|
@@ -22,7 +22,7 @@ require "async/container/hybrid"
|
|
22
22
|
|
23
23
|
require_relative 'shared_examples'
|
24
24
|
|
25
|
-
RSpec.describe Async::Container::Hybrid do
|
25
|
+
RSpec.describe Async::Container::Hybrid, if: Async::Container.fork? do
|
26
26
|
it_behaves_like Async::Container
|
27
27
|
|
28
28
|
it "should be multiprocess" do
|
@@ -21,18 +21,6 @@
|
|
21
21
|
require 'async/rspec/reactor'
|
22
22
|
|
23
23
|
RSpec.shared_examples_for Async::Container do
|
24
|
-
it "can attach terminator" do
|
25
|
-
terminated = false
|
26
|
-
|
27
|
-
subject.attach do
|
28
|
-
terminated = true
|
29
|
-
end
|
30
|
-
|
31
|
-
subject.stop
|
32
|
-
|
33
|
-
expect(terminated).to be_truthy
|
34
|
-
end
|
35
|
-
|
36
24
|
it "can run concurrently" do
|
37
25
|
input, output = IO.pipe
|
38
26
|
|
@@ -58,4 +46,21 @@ RSpec.shared_examples_for Async::Container do
|
|
58
46
|
|
59
47
|
subject.wait
|
60
48
|
end
|
49
|
+
|
50
|
+
describe '#sleep' do
|
51
|
+
it "can sleep for a short time" do
|
52
|
+
subject.spawn do
|
53
|
+
sleep(2)
|
54
|
+
raise "Boom"
|
55
|
+
end
|
56
|
+
|
57
|
+
subject.sleep(1)
|
58
|
+
|
59
|
+
expect(subject.statistics.failures).to be_zero
|
60
|
+
|
61
|
+
subject.wait
|
62
|
+
|
63
|
+
expect(subject.statistics.failures).to_not be_zero
|
64
|
+
end
|
65
|
+
end
|
61
66
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: async-container
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: process-group
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
47
|
+
version: '1.26'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
54
|
+
version: '1.26'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: async-rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -141,15 +141,19 @@ files:
|
|
141
141
|
- Rakefile
|
142
142
|
- async-container.gemspec
|
143
143
|
- examples/container.rb
|
144
|
+
- examples/threads.rb
|
144
145
|
- lib/async/container.rb
|
146
|
+
- lib/async/container/best.rb
|
145
147
|
- lib/async/container/controller.rb
|
148
|
+
- lib/async/container/error.rb
|
146
149
|
- lib/async/container/forked.rb
|
150
|
+
- lib/async/container/generic.rb
|
147
151
|
- lib/async/container/group.rb
|
148
152
|
- lib/async/container/hybrid.rb
|
149
153
|
- lib/async/container/statistics.rb
|
150
|
-
- lib/async/container/terminator.rb
|
151
154
|
- lib/async/container/threaded.rb
|
152
155
|
- lib/async/container/version.rb
|
156
|
+
- spec/async/container/controller_spec.rb
|
153
157
|
- spec/async/container/forked_spec.rb
|
154
158
|
- spec/async/container/hybrid_spec.rb
|
155
159
|
- spec/async/container/shared_examples.rb
|
@@ -176,11 +180,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
180
|
- !ruby/object:Gem::Version
|
177
181
|
version: '0'
|
178
182
|
requirements: []
|
179
|
-
rubygems_version: 3.0.
|
183
|
+
rubygems_version: 3.0.4
|
180
184
|
signing_key:
|
181
185
|
specification_version: 4
|
182
186
|
summary: Async is an asynchronous I/O framework based on nio4r.
|
183
187
|
test_files:
|
188
|
+
- spec/async/container/controller_spec.rb
|
184
189
|
- spec/async/container/forked_spec.rb
|
185
190
|
- spec/async/container/hybrid_spec.rb
|
186
191
|
- spec/async/container/shared_examples.rb
|