Skip to content

Commit 235f061

Browse files
committed
Merge pull request #255 from ruby-concurrency/fix/thread-pool-shutdown
Global configuration and thread pool updates.
2 parents 40080ae + e8c6f8a commit 235f061

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+1284
-717
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ matrix:
2626
- rvm: jruby-head
2727
- rvm: 1.9.3
2828

29-
script: "rake compile && bundle exec rspec --color --backtrace --tag ~unfinished --seed 1 --format documentation ./spec"
29+
script: "rake compile && bundle exec rspec --color --backtrace --tag ~unfinished --seed 1 ./spec"

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,33 @@
1515
* Deprecated all clock-time based timer scheduling
1616
- Only support scheduling by delay
1717
- Effects `Concurrent.timer`, `TimerSet`, and `ScheduledTask`
18+
* Consistent `at_exit` behavior for Java and Ruby thread pools.
19+
* Added `at_exit` handler to Ruby thread pools (already in Java thread pools)
20+
- Ruby handler stores the object id and retrieves from `ObjectSpace`
21+
- JRuby disables `ObjectSpace` by default so that handler stores the object reference
22+
* Added a `:stop_on_exit` option to thread pools to enable/disable `at_exit` handler
23+
* Updated thread pool docs to better explain shutting down thread pools
24+
* Simpler `:executor` option syntax for all abstractions which support this option
25+
* Added `Executor#auto_terminate?` predicate method (for thread pools)
26+
* Added `at_exit` handler to `TimerSet`
27+
* Simplified auto-termination of the global executors
28+
- Can now disable auto-termination of global executors
29+
- Added shutdown/kill/wait_for_termination variants for global executors
30+
* Can now disable auto-termination for *all* executors (the nuclear option)
31+
* Simplified auto-termination of the global executors
32+
* Deprecated terms "task pool" and "operation pool"
33+
- New terms are "io executor" and "fast executor"
34+
- New functions added with new names
35+
- Deprecation warnings added to functions referencing old names
36+
* Moved all thread pool related functions from `Concurrent::Configuration` to `Concurrent`
37+
- Old functions still exist with deprecation warnings
38+
- New functions have updated names as appropriate
39+
* All high-level abstractions default to the "io executor"
40+
* Fixed bug in `Actor` causing it to prematurely warm global thread pools on gem load
41+
- This also fixed a `RejectedExecutionError` bug when running with minitest/autorun via JRuby
42+
* Added `LazyReference`, a simpler and faster varition of `Delay`
43+
- Updated most internal uses of `Delay` with `LazyReference`
44+
* Moved global logger up to the `Concurrent` namespace and refactored the code
1845

1946
## Current Release v0.8.0 (25 January 2015)
2047

Gemfile

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ gemspec name: 'concurrent-ruby'
44

55
group :development do
66
gem 'rake', '~> 10.3.2'
7-
gem 'rake-compiler', '~> 0.9.2'
7+
gem 'rake-compiler', '~> 0.9.5'
88
gem 'gem-compiler', '~> 0.3.0'
99
end
1010

1111
group :testing do
12-
gem 'rspec', '~> 3.0.0'
13-
gem 'simplecov', '~> 0.8.2', :require => false
14-
gem 'coveralls', '~> 0.7.0', :require => false
15-
gem 'timecop', '~> 0.7.1'
12+
gem 'rspec', '~> 3.2.0'
13+
gem 'simplecov', '~> 0.9.2', :require => false
14+
gem 'coveralls', '~> 0.7.11', :require => false
15+
gem 'timecop', '~> 0.7.3'
1616
end
1717

1818
group :documentation do
1919
gem 'countloc', '~> 0.4.0', :platforms => :mri, :require => false
20-
gem 'rubycritic', '~> 1.0.2', :platforms => :mri, require: false
21-
gem 'yard', '~> 0.8.7.4', :require => false
22-
gem 'inch', '~> 0.4.6', :platforms => :mri, :require => false
23-
gem 'redcarpet', '~> 3.1.2', platforms: :mri # understands github markdown
20+
gem 'yard', '~> 0.8.7.6', :require => false
21+
gem 'inch', '~> 0.5.10', :platforms => :mri, :require => false
22+
gem 'redcarpet', '~> 3.2.2', platforms: :mri # understands github markdown
2423
end

README.md

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,36 +66,29 @@ This library contains a variety of concurrency abstractions at high and low leve
6666

6767
* See [ThreadPool](http://ruby-concurrency.github.io/concurrent-ruby/file.thread_pools.html) overview, which also contains a list of other Executors available.
6868

69-
### Thread-safe Observers
70-
71-
* [Concurrent::Observable](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Observable.html) mixin module
72-
* [CopyOnNotifyObserverSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CopyOnNotifyObserverSet.html)
73-
* [CopyOnWriteObserverSet](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CopyOnWriteObserverSet.html)
74-
7569
### Thread synchronization classes and algorithms
7670

77-
Lower-level abstractions mainly used as building blocks.
78-
79-
* [condition](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Condition.html)
80-
* [countdown latch](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CountDownLatch.html)
81-
* [cyclic barrier](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CyclicBarrier.html)
82-
* [event](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Event.html)
83-
* [exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html)
84-
* [semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html)
85-
* [timeout](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#timeout-class_method)
86-
* [timer](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#timer-class_method)
71+
* [Condition](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Condition.html)
72+
* [CountdownLatch](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CountDownLatch.html)
73+
* [CyclicBarrier](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/CyclicBarrier.html)
74+
* [Event](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Event.html)
75+
* [Exchanger](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Exchanger.html)
76+
* [Semaphore](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Semaphore.html)
77+
* [Timeout](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#timeout-class_method)
78+
* [Timer](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent.html#timer-class_method)
8779

8880
### Thread-safe variables
8981

90-
Lower-level abstractions mainly used as building blocks.
91-
9282
* [AtomicBoolean](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicBoolean.html)
9383
* [AtomicFixnum](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/AtomicFixnum.html)
94-
* AtomicReference (no docs currently available, check source)
84+
* [AtomicReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MutexAtomic.html)
85+
* [Delay](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Delay.html)
86+
* [LazyReference](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyReference.html)
87+
* [LazyRegister](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/LazyRegister.html)
9588
* [I-Structures](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/IVar.html) (IVar)
9689
* [M-Structures](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/MVar.html) (MVar)
97-
* [thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html)
98-
* [software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar)
90+
* [Thread-local variables](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadLocalVar.html)
91+
* [Software transactional memory](http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/TVar.html) (TVar)
9992

10093
## Usage
10194

@@ -128,6 +121,8 @@ require 'concurrent/delay' # Concurrent::Delay
128121
require 'concurrent/exchanger' # Concurrent::Exchanger
129122
require 'concurrent/future' # Concurrent::Future
130123
require 'concurrent/ivar' # Concurrent::IVar
124+
require 'concurrent/lazy_register' # Concurrent::LazyRegister
125+
require 'concurrent/lazy_reference' # Concurrent::LazyReference
131126
require 'concurrent/mvar' # Concurrent::MVar
132127
require 'concurrent/promise' # Concurrent::Promise
133128
require 'concurrent/scheduled_task' # Concurrent::ScheduledTask

examples/actor_stress_test.rb

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/usr/bin/env ruby
2+
3+
$: << File.expand_path('../../lib', __FILE__)
4+
5+
require 'benchmark'
6+
require 'optparse'
7+
require 'thread'
8+
require 'rspec/expectations'
9+
10+
require 'concurrent/actor'
11+
12+
class ActorStressTester
13+
include ::RSpec::Matchers
14+
15+
TESTS_PER_RUN = 5
16+
THREADS_PER_TEST = 10
17+
LOOPS_PER_THREAD = 25
18+
19+
class Ping < Concurrent::Actor::Context
20+
def initialize(queue)
21+
@queue = queue
22+
end
23+
24+
def on_message(message)
25+
case message
26+
when :child
27+
Concurrent::Actor::Utils::AdHoc.spawn(:pong, @queue) do |queue|
28+
-> m { queue << m }
29+
end
30+
else
31+
@queue << message
32+
message
33+
end
34+
end
35+
end
36+
37+
def initialize(opts = {})
38+
@tests = opts.fetch(:tests, TESTS_PER_RUN)
39+
@threads = opts.fetch(:threads, THREADS_PER_TEST)
40+
@loops = opts.fetch(:loops, LOOPS_PER_THREAD)
41+
end
42+
43+
def run
44+
plural = ->(number){ number == 1 ? '' : 's' }
45+
46+
puts "Running #{@tests} test#{plural.call(@tests)} " +
47+
"with #{@threads} thread#{plural.call(@threads)} each " +
48+
"and #{@loops} loop#{plural.call(@loops)} per thread..."
49+
50+
Benchmark.bm do |bm|
51+
@tests.times do
52+
bm.report do
53+
test(@threads, @loops)
54+
end
55+
end
56+
end
57+
end
58+
59+
def test(threads, loops)
60+
(1..threads).collect do
61+
Thread.new do
62+
loops.times do
63+
64+
queue = Queue.new
65+
actor = Ping.spawn(:ping, queue)
66+
67+
core = Concurrent::Actor.root.send(:core)
68+
children = core.instance_variable_get(:@children)
69+
expect(children).to include(actor)
70+
71+
actor << 'a' << 1
72+
expect(queue.pop).to eq 'a'
73+
expect(actor.ask(2).value).to eq 2
74+
75+
expect(actor.parent).to eq Concurrent::Actor.root
76+
expect(Concurrent::Actor.root.path).to eq '/'
77+
expect(actor.path).to eq '/ping'
78+
79+
child = actor.ask(:child).value
80+
expect(child.path).to eq '/ping/pong'
81+
82+
queue.clear
83+
child.ask(3)
84+
expect(queue.pop).to eq 3
85+
86+
actor << :terminate!
87+
expect(actor.ask(:blow_up).wait).to be_rejected
88+
terminate_actors(actor, child)
89+
end
90+
end
91+
end.each(&:join)
92+
end
93+
94+
def terminate_actors(*actors)
95+
actors.each do |actor|
96+
unless actor.ask!(:terminated?)
97+
actor.ask!(:terminate!)
98+
end
99+
end
100+
end
101+
end
102+
103+
# def trace!
104+
# set_trace_func proc { |event, file, line, id, binding, classname|
105+
# # thread = eval('Thread.current', binding).object_id.to_s(16)
106+
# printf "%8s %20s %20s %s %s:%-2d\n", event, id, classname, nil, file, line
107+
# }
108+
# yield
109+
# ensure
110+
# set_trace_func nil
111+
# end
112+
113+
if $0 == __FILE__
114+
115+
options = {}
116+
117+
OptionParser.new do |opts|
118+
opts.banner = "Usage: #{File.basename(__FILE__)} [options]"
119+
120+
opts.on("--tests=TESTS", "Number of tests per run") do |value|
121+
options[:tests] = value.to_i
122+
end
123+
124+
opts.on("--threads=THREADS", "Number of threads per test") do |value|
125+
options[:threads] = value.to_i
126+
end
127+
128+
opts.on("--loops=LOOPS", "Number of loops per thread") do |value|
129+
options[:loops] = value.to_i
130+
end
131+
132+
opts.on("-h", "--help", "Prints this help") do
133+
puts opts
134+
exit
135+
end
136+
end.parse!
137+
138+
ActorStressTester.new(options).run
139+
end

examples/lazy_and_delay.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env ruby
2+
3+
$: << File.expand_path('../../lib', __FILE__)
4+
5+
require 'benchmark'
6+
7+
require 'concurrent/delay'
8+
require 'concurrent/lazy_reference'
9+
10+
n = 500_000
11+
12+
delay = Concurrent::Delay.new{ nil }
13+
lazy = Concurrent::LazyReference.new{ nil }
14+
15+
delay.value
16+
lazy.value
17+
18+
Benchmark.bm do |x|
19+
puts 'Benchmarking Delay...'
20+
x.report { n.times{ delay.value } }
21+
puts 'Benchmarking Lazy...'
22+
x.report { n.times{ lazy.value } }
23+
end

lib/concurrent.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,26 @@
22

33
require 'concurrent/configuration'
44

5+
require 'concurrent/actor'
56
require 'concurrent/atomics'
67
require 'concurrent/channels'
78
require 'concurrent/collections'
9+
require 'concurrent/errors'
810
require 'concurrent/executors'
911
require 'concurrent/utilities'
1012

11-
require 'concurrent/actor'
1213
require 'concurrent/atomic'
13-
require 'concurrent/lazy_register'
1414
require 'concurrent/agent'
1515
require 'concurrent/async'
16+
require 'concurrent/atomic'
1617
require 'concurrent/dataflow'
1718
require 'concurrent/delay'
18-
require 'concurrent/dereferenceable'
19-
require 'concurrent/errors'
2019
require 'concurrent/exchanger'
2120
require 'concurrent/future'
2221
require 'concurrent/ivar'
22+
require 'concurrent/lazy_reference'
23+
require 'concurrent/lazy_register'
2324
require 'concurrent/mvar'
24-
require 'concurrent/obligation'
25-
require 'concurrent/observable'
26-
require 'concurrent/options_parser'
2725
require 'concurrent/promise'
2826
require 'concurrent/scheduled_task'
2927
require 'concurrent/timer_task'

lib/concurrent/actor.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'concurrent/configuration'
2+
require 'concurrent/delay'
23
require 'concurrent/executor/serialized_execution'
34
require 'concurrent/ivar'
45
require 'concurrent/logging'
@@ -39,7 +40,7 @@ def self.current
3940
Thread.current[:__current_actor__]
4041
end
4142

42-
@root = Delay.new do
43+
@root = Delay.new(executor: :immediate) do
4344
Core.new(parent: nil, name: '/', class: Root, initialized: ivar = IVar.new).reference.tap do
4445
ivar.no_error!
4546
end
@@ -59,7 +60,7 @@ def self.root
5960
# Actor.spawn name: :ping3,
6061
# class: AdHoc,
6162
# args: [1]
62-
# executor: Concurrent.configuration.global_task_pool do |add|
63+
# executor: Concurrent.global_io_executor do |add|
6364
# lambda { |number| number + add }
6465
# end
6566
#

lib/concurrent/actor/core.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class Core
3535
# @option opts [Class] reference a custom descendant of {Reference} to use
3636
# @option opts [Context] actor_class a class to be instantiated defining Actor's behaviour
3737
# @option opts [Array<Object>] args arguments for actor_class instantiation
38-
# @option opts [Executor] executor, default is `Concurrent.configuration.global_task_pool`
38+
# @option opts [Executor] executor, default is `Concurrent.global_io_executor`
3939
# @option opts [true, false] link, atomically link the actor to its parent
4040
# @option opts [true, false] supervise, atomically supervise the actor by its parent
4141
# @option opts [Array<Array(Behavior::Abstract, Array<Object>)>]
@@ -56,7 +56,7 @@ def initialize(opts = {}, &block)
5656
@context_class = Child! opts.fetch(:class), AbstractContext
5757
allocate_context
5858

59-
@executor = Type! opts.fetch(:executor, Concurrent.configuration.global_task_pool), Executor
59+
@executor = Type! opts.fetch(:executor, Concurrent.global_io_executor), Executor
6060
raise ArgumentError, 'ImmediateExecutor is not supported' if @executor.is_a? ImmediateExecutor
6161

6262
@reference = (Child! opts[:reference_class] || @context.default_reference_class, Reference).new self

lib/concurrent/actor/reference.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ def tell(message)
2727

2828
# @note it's a good practice to use tell whenever possible. Ask should be used only for
2929
# testing and when it returns very shortly. It can lead to deadlock if all threads in
30-
# global_task_pool will block on while asking. It's fine to use it form outside of actors and
31-
# global_task_pool.
30+
# global_io_executor will block on while asking. It's fine to use it form outside of actors and
31+
# global_io_executor.
3232
#
3333
# sends message to the actor and asks for the result of its processing, returns immediately
3434
# @param [Object] message
@@ -40,8 +40,8 @@ def ask(message, ivar = IVar.new)
4040

4141
# @note it's a good practice to use tell whenever possible. Ask should be used only for
4242
# testing and when it returns very shortly. It can lead to deadlock if all threads in
43-
# global_task_pool will block on while asking. It's fine to use it form outside of actors and
44-
# global_task_pool.
43+
# global_io_executor will block on while asking. It's fine to use it form outside of actors and
44+
# global_io_executor.
4545
#
4646
# sends message to the actor and asks for the result of its processing, blocks
4747
# @param [Object] message

0 commit comments

Comments
 (0)