Skip to content

Commit d5b2635

Browse files
committed
Merge pull request #336 from ianks/atomic-ref-try
AtomicReference: update try_update API (resolves #330)
2 parents 185f0f2 + a7dc6b1 commit d5b2635

File tree

3 files changed

+59
-8
lines changed

3 files changed

+59
-8
lines changed

CHANGELOG.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
### Next Release v0.9.0 (Target Date: 7 June 2015)
22

3+
4+
* Updated `AtomicReference`
5+
- `AtomicReference#try_update` now simply returns instead of raising exception
6+
- `AtomicReference#try_update!` was added to raise exceptions if an update
7+
fails. Note: this is the same behavior as the old `try_update`
38
* Pure Java implementations of
49
- `AtomicBoolean`
510
- `AtomicFixnum`
@@ -58,7 +63,7 @@
5863
- `Channel`
5964
- `Exchanger`
6065
- `LazyRegister`
61-
- **new Future Framework** <http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge.html> - unified
66+
- **new Future Framework** <http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Edge.html> - unified
6267
implementation of Futures and Promises which combines Features of previous `Future`,
6368
`Promise`, `IVar`, `Event`, `Probe`, `dataflow`, `Delay`, `TimerTask` into single framework. It uses extensively
6469
new synchronization layer to make all the paths **lock-free** with exception of blocking threads on `#wait`.
@@ -80,7 +85,7 @@
8085
- Add AbstractContext#default_executor to be able to override executor class wide
8186
- Add basic IO example
8287
- Documentation somewhat improved
83-
- All messages should have same priority. It's now possible to send `actor << job1 << job2 << :terminate!` and
88+
- All messages should have same priority. It's now possible to send `actor << job1 << job2 << :terminate!` and
8489
be sure that both jobs are processed first.
8590
* Refactored `Channel` to use newer synchronization objects
8691
* Added `#reset` and `#cancel` methods to `TimerSet`
@@ -162,7 +167,7 @@ Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/iss
162167
- `SerializedExecutionDelegator` for serializing *any* executor
163168
* Updated `Async` with serialized execution
164169
* Updated `ImmediateExecutor` and `PerThreadExecutor` with full executor service lifecycle
165-
* Added a `Delay` to root `Actress` initialization
170+
* Added a `Delay` to root `Actress` initialization
166171
* Minor bug fixes to thread pools
167172
* Refactored many intermittently failing specs
168173
* Removed Java interop warning `executor.rb:148 warning: ambiguous Java methods found, using submit(java.lang.Runnable)`

lib/concurrent/atomic_reference/direct_update.rb

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module AtomicDirectUpdate
1313
# Pass the current value to the given block, replacing it
1414
# with the block's result. May retry if the value changes
1515
# during the block's execution.
16-
#
16+
#
1717
# @yield [Object] Calculate a new value for the atomic reference using
1818
# given (old) value
1919
# @yieldparam [Object] old_value the starting value of the atomic reference
@@ -27,17 +27,45 @@ def update
2727
# @!macro [attach] atomic_reference_method_try_update
2828
#
2929
# Pass the current value to the given block, replacing it
30+
# with the block's result. Return nil if the update fails.
31+
#
32+
# @yield [Object] Calculate a new value for the atomic reference using
33+
# given (old) value
34+
# @yieldparam [Object] old_value the starting value of the atomic reference
35+
#
36+
# @note This method was altered to avoid raising an exception by default.
37+
# Instead, this method now returns `nil` in case of failure. For more info,
38+
# please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
39+
#
40+
# @return [Object] the new value, or nil if update failed
41+
def try_update
42+
old_value = get
43+
new_value = yield old_value
44+
45+
return unless compare_and_set old_value, new_value
46+
47+
new_value
48+
end
49+
50+
# @!macro [attach] atomic_reference_method_try_update!
51+
#
52+
# Pass the current value to the given block, replacing it
3053
# with the block's result. Raise an exception if the update
3154
# fails.
32-
#
55+
#
3356
# @yield [Object] Calculate a new value for the atomic reference using
3457
# given (old) value
3558
# @yieldparam [Object] old_value the starting value of the atomic reference
3659
#
60+
# @note This behavior mimics the behavior of the original
61+
# `AtomicReference#try_update` API. The reason this was changed was to
62+
# avoid raising exceptions (which are inherently slow) by default. For more
63+
# info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
64+
#
3765
# @return [Object] the new value
3866
#
3967
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
40-
def try_update
68+
def try_update!
4169
old_value = get
4270
new_value = yield old_value
4371
unless compare_and_set(old_value, new_value)

spec/concurrent/atomic/atomic_reference_spec.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@
3333
expect(res).to eq 1001
3434
end
3535

36+
specify :test_try_update_bang do
37+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
38+
atomic = described_class.new(1000)
39+
res = atomic.try_update! {|v| v + 1}
40+
41+
expect(atomic.value).to eq 1001
42+
expect(res).to eq 1001
43+
end
44+
3645
specify :test_swap do
3746
atomic = described_class.new(1000)
3847
res = atomic.swap(1001)
@@ -42,12 +51,21 @@
4251
end
4352

4453
specify :test_try_update_fails do
54+
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
55+
atomic = described_class.new(1000)
56+
expect(
57+
# assigning within block exploits implementation detail for test
58+
atomic.try_update {|v| atomic.value = 1001 ; v + 1}
59+
).to be_falsey
60+
end
61+
62+
specify :test_try_update_bang_fails do
4563
# use a number outside JRuby's fixnum cache range, to ensure identity is preserved
4664
atomic = described_class.new(1000)
4765
expect {
4866
# assigning within block exploits implementation detail for test
49-
atomic.try_update{|v| atomic.value = 1001 ; v + 1}
50-
}.to raise_error(Concurrent::ConcurrentUpdateError)
67+
atomic.try_update! {|v| atomic.value = 1001 ; v + 1}
68+
}.to raise_error Concurrent::ConcurrentUpdateError
5169
end
5270

5371
specify :test_update_retries do

0 commit comments

Comments
 (0)