Skip to content

Commit 7fdc1dd

Browse files
committed
Fix Edge::Future#value and others when specifying timeouts
1 parent 1831906 commit 7fdc1dd

File tree

3 files changed

+49
-30
lines changed

3 files changed

+49
-30
lines changed

concurrent-ruby-edge.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
1010
s.name = 'concurrent-ruby-edge'
1111
s.version = Concurrent::EDGE_VERSION
1212
s.platform = Gem::Platform::RUBY
13-
s.authors = ["Jerry D'Antonio", 'The Ruby Concurrency Team']
13+
s.authors = ["Jerry D'Antonio", 'Petr Chalupa', 'The Ruby Concurrency Team']
1414
s.email = ['jerry.dantonio@gmail.com', 'concurrent-ruby@googlegroups.com']
1515
s.homepage = 'http://www.concurrent-ruby.com'
1616
s.summary = 'Edge features and additions to the concurrent-ruby gem.'

lib/concurrent/edge.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ module Concurrent
2121
#
2222
# @!macro [attach] edge_warning
2323
# @api Edge
24-
# @note **Edge Feature:** Edge features are under active development and may
25-
# change frequently. They are not expected to keep backward compatibility.
26-
# They may also lack tests and documentation. Use with caution.
24+
# @note **Edge Feature:** Edge features are under active development and may change frequently. They are expected not to
25+
# keep backward compatibility (there may also lack tests and documentation). Semantic versions will
26+
# be obeyed though. Features developed in `concurrent-ruby-edge` are expected to move
27+
# to `concurrent-ruby` when final.
2728
module Edge
28-
2929
end
3030
end

lib/concurrent/edge/future.rb

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ def post_on(executor, *args, &job)
121121
class Event < Synchronization::Object
122122
include Concern::Deprecation
123123

124+
# @!visibility private
124125
class State
125126
def completed?
126127
raise NotImplementedError
@@ -131,6 +132,7 @@ def to_sym
131132
end
132133
end
133134

135+
# @!visibility private
134136
class Pending < State
135137
def completed?
136138
false
@@ -141,6 +143,7 @@ def to_sym
141143
end
142144
end
143145

146+
# @!visibility private
144147
class Completed < State
145148
def completed?
146149
true
@@ -151,7 +154,9 @@ def to_sym
151154
end
152155
end
153156

157+
# @!visibility private
154158
PENDING = Pending.new
159+
# @!visibility private
155160
COMPLETED = Completed.new
156161

157162
def initialize(promise, default_executor)
@@ -192,11 +197,14 @@ def completed?(state = @State.get)
192197

193198
# Wait until Event is #complete?
194199
# @param [Numeric] timeout the maximum time in second to wait.
195-
# @return [Event] self
200+
# @return [Event, true, false] self or true/false if timeout is used
201+
# @!macro [attach] edge.periodical_wait
202+
# @note a thread should wait only once! For repeated checking use faster `completed?` check.
203+
# If thread waits periodically it will dangerously grow the waiters stack.
196204
def wait(timeout = nil)
197205
touch
198-
wait_until_complete timeout
199-
self
206+
result = wait_until_complete(timeout)
207+
timeout ? result : self
200208
end
201209

202210
# @!visibility private
@@ -335,20 +343,19 @@ def waiting_threads
335343

336344
private
337345

346+
# @return [true, false]
338347
def wait_until_complete(timeout)
339348
while true
340349
last_waiter = @Waiters.peek # waiters' state before completion
341-
break if completed?
350+
return true if completed?
342351

343352
# synchronize so it cannot be signaled before it waits
344353
synchronize do
345354
# ok only if completing thread did not start signaling
346355
next unless @Waiters.compare_and_push last_waiter, Thread.current
347-
ns_wait_until(timeout) { completed? }
356+
return ns_wait_until(timeout) { completed? }
348357
end
349-
break
350358
end
351-
self
352359
end
353360

354361
def complete_state
@@ -386,6 +393,7 @@ def call_callbacks
386393

387394
# Represents a value which will become available in future. May fail with a reason instead.
388395
class Future < Event
396+
# @!visibility private
389397
class CompletedWithResult < Completed
390398
def result
391399
[success?, value, reason]
@@ -404,6 +412,7 @@ def reason
404412
end
405413
end
406414

415+
# @!visibility private
407416
class Success < CompletedWithResult
408417
def initialize(value)
409418
@Value = value
@@ -430,12 +439,14 @@ def to_sym
430439
end
431440
end
432441

442+
# @!visibility private
433443
class SuccessArray < Success
434444
def apply(block)
435445
block.call *value
436446
end
437447
end
438448

449+
# @!visibility private
439450
class Failed < CompletedWithResult
440451
def initialize(reason)
441452
@Reason = reason
@@ -468,7 +479,7 @@ def apply(block)
468479
# Has Future been success?
469480
# @return [Boolean]
470481
def success?(state = @State.get)
471-
state.success?
482+
state.completed? && state.success?
472483
end
473484

474485
def fulfilled?
@@ -479,52 +490,60 @@ def fulfilled?
479490
# Has Future been failed?
480491
# @return [Boolean]
481492
def failed?(state = @State.get)
482-
!success?(state)
493+
state.completed? && !state.success?
483494
end
484495

485496
def rejected?
486497
deprecated_method 'rejected?', 'failed?'
487498
failed?
488499
end
489500

490-
# @return [Object] the value of the Future when success
501+
# @return [Object, nil] the value of the Future when success, nil on timeout
502+
# @!macro [attach] edge.timeout_nil
503+
# @note If the Future can have value `nil` then it cannot be distinquished from `nil` returned on timeout.
504+
# In this case is better to use first `wait` then `value` (or similar).
505+
# @!macro edge.periodical_wait
491506
def value(timeout = nil)
492507
touch
493-
wait_until_complete timeout
494-
@State.get.value
508+
@State.get.value if wait_until_complete timeout
495509
end
496510

497-
# @return [Exception] the reason of the Future's failure
511+
# @return [Exception, nil] the reason of the Future's failure
512+
# @!macro edge.timeout_nil
513+
# @!macro edge.periodical_wait
498514
def reason(timeout = nil)
499515
touch
500-
wait_until_complete timeout
501-
@State.get.reason
516+
@State.get.reason if wait_until_complete timeout
502517
end
503518

504-
# @return [Array(Boolean, Object, Exception)] triplet of success, value, reason
519+
# @return [Array(Boolean, Object, Exception), nil] triplet of success, value, reason
520+
# @!macro edge.timeout_nil
521+
# @!macro edge.periodical_wait
505522
def result(timeout = nil)
506523
touch
507-
wait_until_complete timeout
508-
@State.get.result
524+
@State.get.result if wait_until_complete timeout
509525
end
510526

511527
# Wait until Future is #complete?
512528
# @param [Numeric] timeout the maximum time in second to wait.
513529
# @raise reason on failure
514-
# @return [Event] self
530+
# @return [Event, true, false] self or true/false if timeout is used
531+
# @!macro edge.periodical_wait
515532
def wait!(timeout = nil)
516533
touch
517-
wait_until_complete! timeout
534+
result = wait_until_complete!(timeout)
535+
timeout ? result : self
518536
end
519537

520538
# Wait until Future is #complete?
521539
# @param [Numeric] timeout the maximum time in second to wait.
522540
# @raise reason on failure
523-
# @return [Object]
541+
# @return [Object, nil]
542+
# @!macro edge.timeout_nil
543+
# @!macro edge.periodical_wait
524544
def value!(timeout = nil)
525545
touch
526-
wait_until_complete!(timeout)
527-
@State.get.value
546+
@State.get.value if wait_until_complete! timeout
528547
end
529548

530549
# @example allows failed Future to be risen
@@ -631,9 +650,9 @@ def apply(block)
631650
private
632651

633652
def wait_until_complete!(timeout = nil)
634-
wait_until_complete(timeout)
653+
result = wait_until_complete(timeout)
635654
raise self if failed?
636-
self
655+
result
637656
end
638657

639658
def complete_state(success, value, reason)

0 commit comments

Comments
 (0)