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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1b28eaf57dab7a59a62f755fcedf993c1e666e11308bbc2bbf1504d6e43d7a5
4
- data.tar.gz: 8e80d9fadf1491d7e8b6ff1827c27d72f5d4ba120f6afbccf021c760c63d4f34
3
+ metadata.gz: 4f6640daaa86e57d05f5c7ec2a0d25d98b355edb0926d45ba1e2414adfdaa46e
4
+ data.tar.gz: fd25c58be73c64bf64dec42ac24e84b9f5c66fdb26da9780246c877bd38056f5
5
5
  SHA512:
6
- metadata.gz: 262d775565084f15cab838bdc72ce84581f8f4ee879e3a003a75c163479ac62a172736956c56a9d5524eefb80194d3aa3aed4b6239f6ddbeaea91713d8572f22
7
- data.tar.gz: 143eec3c82a23f3b804784850556e5a3b8dd455608a0f3b98ace66583a024a6c5e20819a129ed17121d04bc849bfe42573b5405331f4ef7f360254c0588b4434
6
+ metadata.gz: e6011d00efba48db1d17fedf7fe9823210c58840611a140710be8ce88a55e39b5421bebf666d97a4741f5c97c1f16758f6bc7598fd4205ffb0b2c975173e496f
7
+ data.tar.gz: d0c020e874296fc31abdd6638d7113ddb0ff7e718e142c9c8c77b40ad7874e848a78b9e351e45f9439421d1629d58cb20fe086d3966d07345c3cb46fe9a517a3
data/.travis.yml CHANGED
@@ -17,6 +17,5 @@ matrix:
17
17
  env: JRUBY_OPTS="--debug -X+O"
18
18
  - rvm: ruby-head
19
19
  allow_failures:
20
- - rvm: truffleruby
21
20
  - rvm: ruby-head
22
21
  - rvm: jruby-head
@@ -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.4"
26
+ spec.add_runtime_dependency "async-io", "~> 1.26"
27
27
 
28
28
  spec.add_development_dependency "async-rspec", "~> 1.1"
29
29
 
@@ -1,21 +1,32 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'async/container/forked'
3
+ require '../lib/async/container/controller'
4
+ require '../lib/async/container/forked'
4
5
 
5
- container = Async::Container::Forked.new
6
+ Async.logger.debug!
6
7
 
7
- puts "Controller process: #{Process.pid}"
8
+ Async.logger.debug(self, "Starting up...")
8
9
 
9
- container.run(processes: 8, restart: true) do
10
- puts "Starting process: #{Process.pid}"
11
-
12
- while true
13
- sleep 1
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
- container.wait
20
-
21
- puts "Controller procss exiting!"
28
+ begin
29
+ controller.run
30
+ ensure
31
+ Async.logger.debug(controller, "Parent process exiting:", $!)
32
+ end
@@ -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"
@@ -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/forked'
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 'terminator'
21
+ require_relative 'error'
22
+ require_relative 'best'
22
23
 
23
- require 'async/reactor'
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
- def initialize
29
- @attached = []
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 attach(controller = nil, &block)
33
- if controller
34
- @attached << controller
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
- if block_given?
38
- @attached << Terminator.new(&block)
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
- return self
86
+ @container&.stop
87
+ @container = container
88
+ ensure
89
+ Signal.trap(:HUP, hup_action)
42
90
  end
43
91
 
44
- def async(**options, &block)
45
- spawn(**options) do |instance|
92
+ def run
93
+ Async.logger.debug(self) {"Starting container..."}
94
+
95
+ self.start
96
+
97
+ while true
46
98
  begin
47
- Async::Reactor.run(instance, &block)
48
- rescue Interrupt
49
- # Graceful exit.
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
- end
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
@@ -20,14 +20,7 @@
20
20
 
21
21
  module Async
22
22
  module Container
23
- class Terminator
24
- def initialize(&block)
25
- @block = block
26
- end
27
-
28
- def stop(graceful = true)
29
- @block.call(graceful)
30
- end
23
+ class Error < StandardError
31
24
  end
32
25
  end
33
26
  end
@@ -21,13 +21,13 @@
21
21
  require 'async/reactor'
22
22
 
23
23
  require_relative 'group'
24
- require_relative 'controller'
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 < Controller
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 wait(forever = false)
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
- wait_all
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
- wait_all
90
+ interrupt_all
89
91
  end
90
92
 
91
93
  protected
92
94
 
93
- def wait_all
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.
@@ -44,6 +44,16 @@ module Async
44
44
  def failure!
45
45
  @failures += 1
46
46
  end
47
+
48
+ def failed?
49
+ @failures > 0
50
+ end
51
+
52
+ def << other
53
+ @spawns += other.spawns
54
+ @restarts += other.restarts
55
+ @failures += other.failures
56
+ end
47
57
  end
48
58
  end
49
59
  end
@@ -21,13 +21,13 @@
21
21
  require 'async/reactor'
22
22
  require 'thread'
23
23
 
24
- require_relative 'controller'
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 < Controller
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 wait(forever = false)
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)}
@@ -20,6 +20,6 @@
20
20
 
21
21
  module Async
22
22
  module Container
23
- VERSION = "0.14.1"
23
+ VERSION = "0.15.0"
24
24
  end
25
25
  end
@@ -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.14.1
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-06-09 00:00:00.000000000 Z
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.4'
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.4'
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.3
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