concurrent-ruby 0.6.0 → 0.6.1

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.
@@ -31,7 +31,12 @@ module Concurrent
31
31
  core.executor
32
32
  end
33
33
 
34
+ def actor_class
35
+ core.actor_class
36
+ end
37
+
34
38
  alias_method :ref, :reference
39
+ alias_method :actress_class, :actor_class
35
40
  end
36
41
  end
37
42
  end
@@ -1,12 +1,24 @@
1
1
  module Concurrent
2
2
  module Actress
3
- Envelope = Struct.new :message, :ivar, :sender do
3
+ class Envelope
4
4
  include TypeCheck
5
5
 
6
- def initialize(message, ivar, sender)
7
- super message,
8
- (Type! ivar, IVar, NilClass),
9
- (Type! sender, Reference, Thread)
6
+ # @!attribute [r] message
7
+ # @return [Object] a message
8
+ # @!attribute [r] ivar
9
+ # @return [IVar] an ivar which becomes resolved after message is processed
10
+ # @!attribute [r] sender
11
+ # @return [Reference, Thread] an actor or thread sending the message
12
+ # @!attribute [r] address
13
+ # @return [Reference] where this message will be delivered
14
+
15
+ attr_reader :message, :ivar, :sender, :address
16
+
17
+ def initialize(message, ivar, sender, address)
18
+ @message = message
19
+ @ivar = Type! ivar, IVar, NilClass
20
+ @sender = Type! sender, Reference, Thread
21
+ @address = Type! address, Reference
10
22
  end
11
23
 
12
24
  def sender_path
@@ -17,6 +29,10 @@ module Concurrent
17
29
  end
18
30
  end
19
31
 
32
+ def address_path
33
+ address.path
34
+ end
35
+
20
36
  def reject!(error)
21
37
  ivar.fail error unless ivar.nil?
22
38
  end
@@ -2,8 +2,8 @@ module Concurrent
2
2
  module Actress
3
3
 
4
4
  # Reference is public interface of Actor instances. It is used for sending messages and can
5
- # be freely passed around the program. It also provides some basic information about the actor
6
- # see {CoreDelegations}
5
+ # be freely passed around the program. It also provides some basic information about the actor,
6
+ # see {CoreDelegations}.
7
7
  class Reference
8
8
  include TypeCheck
9
9
  include CoreDelegations
@@ -43,14 +43,14 @@ module Concurrent
43
43
  ask(message, ivar).value!
44
44
  end
45
45
 
46
- # behaves as #tell when no ivar and as #ask when ivar
46
+ # behaves as {#tell} when no ivar and as {#ask} when ivar
47
47
  def message(message, ivar = nil)
48
- core.on_envelope Envelope.new(message, ivar, Actress.current || Thread.current)
48
+ core.on_envelope Envelope.new(message, ivar, Actress.current || Thread.current, self)
49
49
  return ivar || self
50
50
  end
51
51
 
52
52
  def to_s
53
- "#<#{self.class} #{path}>"
53
+ "#<#{self.class} #{path} (#{actor_class})>"
54
54
  end
55
55
 
56
56
  alias_method :inspect, :to_s
@@ -62,14 +62,14 @@ module Concurrent
62
62
  # @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
63
63
  # returning the value returned from the proc
64
64
  def initialize(initial, opts = {})
65
- @value = initial
66
- @rescuers = []
67
- @validator = Proc.new { |result| true }
68
- @timeout = opts.fetch(:timeout, TIMEOUT).freeze
69
- self.observers = CopyOnWriteObserverSet.new
70
- @one_by_one = OneByOne.new
71
- @task_executor = OptionsParser.get_task_executor_from(opts)
72
- @operation_executor = OptionsParser.get_operation_executor_from(opts)
65
+ @value = initial
66
+ @rescuers = []
67
+ @validator = Proc.new { |result| true }
68
+ @timeout = opts.fetch(:timeout, TIMEOUT).freeze
69
+ self.observers = CopyOnWriteObserverSet.new
70
+ @serialized_execution = SerializedExecution.new
71
+ @task_executor = OptionsParser.get_task_executor_from(opts)
72
+ @operation_executor = OptionsParser.get_operation_executor_from(opts)
73
73
  init_mutex
74
74
  set_deref_options(opts)
75
75
  end
@@ -180,7 +180,7 @@ module Concurrent
180
180
 
181
181
  def post_on(executor, &block)
182
182
  return nil if block.nil?
183
- @one_by_one.post(executor) { work(&block) }
183
+ @serialized_execution.post(executor) { work(&block) }
184
184
  true
185
185
  end
186
186
 
@@ -1,6 +1,7 @@
1
1
  require 'thread'
2
2
  require 'concurrent/delay'
3
3
  require 'concurrent/errors'
4
+ require 'concurrent/atomic/atomic'
4
5
  require 'concurrent/executor/thread_pool_executor'
5
6
  require 'concurrent/executor/timer_set'
6
7
  require 'concurrent/utility/processor_count'
@@ -111,8 +112,12 @@ module Concurrent
111
112
  end
112
113
 
113
114
  # create the default configuration on load
114
- @configuration = Configuration.new
115
- singleton_class.send :attr_reader, :configuration
115
+ @configuration = Atomic.new Configuration.new
116
+
117
+ # @return [Configuration]
118
+ def self.configuration
119
+ @configuration.value
120
+ end
116
121
 
117
122
  # Perform gem-level configuration.
118
123
  #
@@ -172,7 +172,6 @@ module Concurrent
172
172
 
173
173
  # @!visibility private
174
174
  def shutdown_execution
175
- @queue.clear
176
175
  if @pool.empty?
177
176
  stopped_event.set
178
177
  else
@@ -1,8 +1,10 @@
1
+ require 'concurrent/logging'
2
+
1
3
  module Concurrent
2
4
 
3
- # Ensures that jobs are passed to the given executors one by one,
4
- # never running at the same time.
5
- class OneByOne
5
+ # Ensures passed jobs in a serialized order never running at the same time.
6
+ class SerializedExecution
7
+ include Logging
6
8
 
7
9
  Job = Struct.new(:executor, :args, :block) do
8
10
  def call
@@ -30,10 +32,6 @@ module Concurrent
30
32
  # @raise [ArgumentError] if no task is given
31
33
  def post(executor, *args, &task)
32
34
  return nil if task.nil?
33
- # FIXME Agent#send-off will blow up here
34
- # if executor.can_overflow?
35
- # raise ArgumentError, 'OneByOne cannot be used in conjunction with executor which may overflow'
36
- # end
37
35
 
38
36
  job = Job.new executor, args, task
39
37
 
@@ -56,7 +54,22 @@ module Concurrent
56
54
  private
57
55
 
58
56
  def call_job(job)
59
- job.executor.post { work(job) }
57
+ did_it_run = begin
58
+ job.executor.post { work(job) }
59
+ true
60
+ rescue RejectedExecutionError => ex
61
+ false
62
+ end
63
+
64
+ # TODO not the best idea to run it myself
65
+ unless did_it_run
66
+ begin
67
+ work job
68
+ rescue => ex
69
+ # let it fail
70
+ log DEBUG, ex
71
+ end
72
+ end
60
73
  end
61
74
 
62
75
  # ensures next job is executed if any is stashed
@@ -61,7 +61,12 @@ module Concurrent
61
61
 
62
62
  end
63
63
 
64
- alias_method :kill, :shutdown
64
+ # For a timer, #kill is like an orderly shutdown, except we need to manually
65
+ # (and destructively) clear the queue first
66
+ def kill
67
+ @queue.clear
68
+ shutdown
69
+ end
65
70
 
66
71
  # Calculate an Epoch time with milliseconds at which to execute a
67
72
  # task. If the given time is a `Time` object it will be converted
@@ -6,4 +6,4 @@ require 'concurrent/executor/safe_task_executor'
6
6
  require 'concurrent/executor/single_thread_executor'
7
7
  require 'concurrent/executor/thread_pool_executor'
8
8
  require 'concurrent/executor/timer_set'
9
- require 'concurrent/executor/one_by_one'
9
+ require 'concurrent/executor/serialized_execution'
@@ -9,7 +9,7 @@ module Concurrent
9
9
  # @param [Integer] level one of Logger::Severity constants
10
10
  # @param [String] progname e.g. a path of an Actor
11
11
  # @param [String, nil] message when nil block is used to generate the message
12
- # @yields_return [String] a message
12
+ # @yieldreturn [String] a message
13
13
  def log(level, progname, message = nil, &block)
14
14
  (@logger || Concurrent.configuration.logger).call level, progname, message, &block
15
15
  end
@@ -10,7 +10,7 @@ module Concurrent
10
10
  observers.add_observer(*args, &block)
11
11
  end
12
12
 
13
- # as #add_observer but it can be used for chaning
13
+ # as #add_observer but it can be used for chaining
14
14
  # @return [Observable] self
15
15
  def with_observer(*args, &block)
16
16
  add_observer *args, &block
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.6.0'
2
+ VERSION = '0.6.1'
3
3
  end
@@ -3,7 +3,27 @@ require 'concurrent/actress'
3
3
 
4
4
  module Concurrent
5
5
  module Actress
6
+ i_know_it_is_experimental!
7
+
8
+ class Reference
9
+ def backdoor(&block)
10
+ core.send :schedule_execution do
11
+ core.instance_eval &block
12
+ end
13
+ end
14
+ end
15
+
6
16
  describe 'Concurrent::Actress' do
17
+ prepend_before do
18
+ do_no_reset!
19
+ end
20
+
21
+ def terminate_actors(*actors)
22
+ actors.each do |actor|
23
+ actor.backdoor { terminate! }
24
+ actor.terminated.wait
25
+ end
26
+ end
7
27
 
8
28
  class Ping
9
29
  include Context
@@ -35,82 +55,80 @@ module Concurrent
35
55
  # set_trace_func nil
36
56
  # end
37
57
 
38
- #describe 'stress test' do
39
- #pending('may cause deadlock which prevents test run from completing.')
40
- #1.times do |i|
41
- #it format('run %3d', i) do
42
- ## puts format('run %3d', i)
43
- #Array.new(10).map do
44
- #Thread.new do
45
- #10.times do
46
- ## trace! do
47
- #queue = Queue.new
48
- #actor = Ping.spawn :ping, queue
49
-
50
- ## when spawn returns children are set
51
- #Concurrent::Actress::ROOT.send(:core).instance_variable_get(:@children).should include(actor)
52
-
53
- #actor << 'a' << 1
54
- #queue.pop.should eq 'a'
55
- #actor.ask(2).value.should eq 2
56
-
57
- #actor.parent.should eq Concurrent::Actress::ROOT
58
- #Concurrent::Actress::ROOT.path.should eq '/'
59
- #actor.path.should eq '/ping'
60
- #child = actor.ask(:child).value
61
- #child.path.should eq '/ping/pong'
62
- #queue.clear
63
- #child.ask(3)
64
- #queue.pop.should eq 3
65
-
66
- #actor << :terminate
67
- #actor.ask(:blow_up).wait.should be_rejected
68
- #end
69
- #end
70
- #end.each(&:join)
71
- #end
72
- #end
73
- #end
58
+ describe 'stress test' do
59
+ 1.times do |i|
60
+ it format('run %3d', i) do
61
+ # puts format('run %3d', i)
62
+ Array.new(10).map do
63
+ Thread.new do
64
+ 10.times do
65
+ # trace! do
66
+ queue = Queue.new
67
+ actor = Ping.spawn :ping, queue
68
+
69
+ # when spawn returns children are set
70
+ Concurrent::Actress::ROOT.send(:core).instance_variable_get(:@children).should include(actor)
71
+
72
+ actor << 'a' << 1
73
+ queue.pop.should eq 'a'
74
+ actor.ask(2).value.should eq 2
75
+
76
+ actor.parent.should eq Concurrent::Actress::ROOT
77
+ Concurrent::Actress::ROOT.path.should eq '/'
78
+ actor.path.should eq '/ping'
79
+ child = actor.ask(:child).value
80
+ child.path.should eq '/ping/pong'
81
+ queue.clear
82
+ child.ask(3)
83
+ queue.pop.should eq 3
84
+
85
+ actor << :terminate
86
+ actor.ask(:blow_up).wait.should be_rejected
87
+ terminate_actors actor, child
88
+ end
89
+ end
90
+ end.each(&:join)
91
+ end
92
+ end
93
+ end
74
94
 
75
95
  describe 'spawning' do
76
- #describe 'Actress#spawn' do
77
- #behaviour = -> v { -> _ { v } }
78
- #subjects = { spawn: -> { Actress.spawn(AdHoc, :ping, 'arg', &behaviour) },
79
- #context_spawn: -> { AdHoc.spawn(:ping, 'arg', &behaviour) },
80
- #spawn_by_hash: -> { Actress.spawn(class: AdHoc, name: :ping, args: ['arg'], &behaviour) },
81
- #context_spawn_by_hash: -> { AdHoc.spawn(name: :ping, args: ['arg'], &behaviour) } }
82
-
83
- #subjects.each do |desc, subject_definition|
84
- #describe desc do
85
- #subject &subject_definition
86
- #its(:path) { pending('may cause deadlock which prevents test run from completing.'); should eq '/ping' }
87
- #its(:parent) { pending('may cause deadlock which prevents test run from completing.'); should eq ROOT }
88
- #its(:name) { pending('may cause deadlock which prevents test run from completing.'); should eq 'ping' }
89
- #its(:executor) { pending('may cause deadlock which prevents test run from completing.'); should eq Concurrent.configuration.global_task_pool }
90
- #its(:reference) { pending('may cause deadlock which prevents test run from completing.'); should eq subject }
91
- #it 'returns ars' do
92
- #subject.ask!(:anything).should eq 'arg'
93
- #end
94
- #end
95
- #end
96
- #end
96
+ describe 'Actress#spawn' do
97
+ behaviour = -> v { -> _ { v } }
98
+ subjects = { spawn: -> { Actress.spawn(AdHoc, :ping, 'arg', &behaviour) },
99
+ context_spawn: -> { AdHoc.spawn(:ping, 'arg', &behaviour) },
100
+ spawn_by_hash: -> { Actress.spawn(class: AdHoc, name: :ping, args: ['arg'], &behaviour) },
101
+ context_spawn_by_hash: -> { AdHoc.spawn(name: :ping, args: ['arg'], &behaviour) } }
102
+
103
+ subjects.each do |desc, subject_definition|
104
+ describe desc do
105
+ subject &subject_definition
106
+ after { terminate_actors subject }
107
+ its(:path) { should eq '/ping' }
108
+ its(:parent) { should eq ROOT }
109
+ its(:name) { should eq 'ping' }
110
+ it('executor should be global') { subject.executor.should eq Concurrent.configuration.global_task_pool }
111
+ its(:reference) { should eq subject }
112
+ it 'returns ars' do
113
+ subject.ask!(:anything).should eq 'arg'
114
+ end
115
+ end
116
+ end
117
+ end
97
118
 
98
119
  it 'terminates on failed initialization' do
99
- pending('may cause deadlock which prevents test run from completing.')
100
120
  a = AdHoc.spawn(name: :fail, logger: Concurrent.configuration.no_logger) { raise }
101
121
  a.ask(nil).wait.rejected?.should be_true
102
122
  a.terminated?.should be_true
103
123
  end
104
124
 
105
125
  it 'terminates on failed initialization and raises with spawn!' do
106
- pending('may cause deadlock which prevents test run from completing.')
107
126
  expect do
108
127
  AdHoc.spawn!(name: :fail, logger: Concurrent.configuration.no_logger) { raise 'm' }
109
128
  end.to raise_error(StandardError, 'm')
110
129
  end
111
130
 
112
131
  it 'terminates on failed message processing' do
113
- pending('may cause deadlock which prevents test run from completing.')
114
132
  a = AdHoc.spawn(name: :fail, logger: Concurrent.configuration.no_logger) { -> _ { raise } }
115
133
  a.ask(nil).wait.rejected?.should be_true
116
134
  a.terminated?.should be_true
@@ -120,11 +138,11 @@ module Concurrent
120
138
  describe 'messaging' do
121
139
  subject { AdHoc.spawn(:add) { c = 0; -> v { c = c + v } } }
122
140
  specify do
123
- pending('may cause deadlock which prevents test run from completing.')
124
141
  subject.tell(1).tell(1)
125
142
  subject << 1 << 1
126
143
  subject.ask(0).value!.should eq 4
127
144
  end
145
+ after { terminate_actors subject }
128
146
  end
129
147
 
130
148
  describe 'children' do
@@ -141,23 +159,24 @@ module Concurrent
141
159
  end
142
160
 
143
161
  it 'has children set after a child is created' do
144
- pending('may cause deadlock which prevents test run from completing.')
145
162
  child = parent.ask!(:child)
146
163
  parent.ask!(nil).should include(child)
147
164
  child.ask!(nil).should eq parent
165
+
166
+ terminate_actors parent, child
148
167
  end
149
168
  end
150
169
 
151
170
  describe 'envelope' do
152
171
  subject { AdHoc.spawn(:subject) { -> _ { envelope } } }
153
172
  specify do
154
- pending('may cause deadlock which prevents test run from completing.')
155
173
  envelope = subject.ask!('a')
156
174
  envelope.should be_a_kind_of Envelope
157
175
  envelope.message.should eq 'a'
158
176
  envelope.ivar.should be_completed
159
177
  envelope.ivar.value.should eq envelope
160
178
  envelope.sender.should eq Thread.current
179
+ terminate_actors subject
161
180
  end
162
181
  end
163
182
 
@@ -176,13 +195,14 @@ module Concurrent
176
195
  end
177
196
 
178
197
  it 'terminates with all its children' do
179
- pending('may cause deadlock which prevents test run from completing.')
180
198
  child = subject.ask! :child
181
199
  subject.terminated?.should be_false
182
200
  subject.ask(:terminate).wait
183
201
  subject.terminated?.should be_true
184
202
  child.terminated.wait
185
203
  child.terminated?.should be_true
204
+
205
+ terminate_actors subject, child
186
206
  end
187
207
  end
188
208