This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
require 'thread'
|
||||
require 'timeout'
|
||||
|
||||
require 'concurrent/atomic/event'
|
||||
require 'concurrent/concern/dereferenceable'
|
||||
|
||||
module Concurrent
|
||||
module Concern
|
||||
|
||||
module Obligation
|
||||
include Concern::Dereferenceable
|
||||
# NOTE: The Dereferenceable module is going away in 2.0. In the mean time
|
||||
# we need it to place nicely with the synchronization layer. This means
|
||||
# that the including class SHOULD be synchronized and it MUST implement a
|
||||
# `#synchronize` method. Not doing so will lead to runtime errors.
|
||||
|
||||
# Has the obligation been fulfilled?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def fulfilled?
|
||||
state == :fulfilled
|
||||
end
|
||||
alias_method :realized?, :fulfilled?
|
||||
|
||||
# Has the obligation been rejected?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def rejected?
|
||||
state == :rejected
|
||||
end
|
||||
|
||||
# Is obligation completion still pending?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def pending?
|
||||
state == :pending
|
||||
end
|
||||
|
||||
# Is the obligation still unscheduled?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def unscheduled?
|
||||
state == :unscheduled
|
||||
end
|
||||
|
||||
# Has the obligation completed processing?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def complete?
|
||||
[:fulfilled, :rejected].include? state
|
||||
end
|
||||
|
||||
# Is the obligation still awaiting completion of processing?
|
||||
#
|
||||
# @return [Boolean]
|
||||
def incomplete?
|
||||
! complete?
|
||||
end
|
||||
|
||||
# The current value of the obligation. Will be `nil` while the state is
|
||||
# pending or the operation has been rejected.
|
||||
#
|
||||
# @param [Numeric] timeout the maximum time in seconds to wait.
|
||||
# @return [Object] see Dereferenceable#deref
|
||||
def value(timeout = nil)
|
||||
wait timeout
|
||||
deref
|
||||
end
|
||||
|
||||
# Wait until obligation is complete or the timeout has been reached.
|
||||
#
|
||||
# @param [Numeric] timeout the maximum time in seconds to wait.
|
||||
# @return [Obligation] self
|
||||
def wait(timeout = nil)
|
||||
event.wait(timeout) if timeout != 0 && incomplete?
|
||||
self
|
||||
end
|
||||
|
||||
# Wait until obligation is complete or the timeout is reached. Will re-raise
|
||||
# any exceptions raised during processing (but will not raise an exception
|
||||
# on timeout).
|
||||
#
|
||||
# @param [Numeric] timeout the maximum time in seconds to wait.
|
||||
# @return [Obligation] self
|
||||
# @raise [Exception] raises the reason when rejected
|
||||
def wait!(timeout = nil)
|
||||
wait(timeout).tap { raise self if rejected? }
|
||||
end
|
||||
alias_method :no_error!, :wait!
|
||||
|
||||
# The current value of the obligation. Will be `nil` while the state is
|
||||
# pending or the operation has been rejected. Will re-raise any exceptions
|
||||
# raised during processing (but will not raise an exception on timeout).
|
||||
#
|
||||
# @param [Numeric] timeout the maximum time in seconds to wait.
|
||||
# @return [Object] see Dereferenceable#deref
|
||||
# @raise [Exception] raises the reason when rejected
|
||||
def value!(timeout = nil)
|
||||
wait(timeout)
|
||||
if rejected?
|
||||
raise self
|
||||
else
|
||||
deref
|
||||
end
|
||||
end
|
||||
|
||||
# The current state of the obligation.
|
||||
#
|
||||
# @return [Symbol] the current state
|
||||
def state
|
||||
synchronize { @state }
|
||||
end
|
||||
|
||||
# If an exception was raised during processing this will return the
|
||||
# exception object. Will return `nil` when the state is pending or if
|
||||
# the obligation has been successfully fulfilled.
|
||||
#
|
||||
# @return [Exception] the exception raised during processing or `nil`
|
||||
def reason
|
||||
synchronize { @reason }
|
||||
end
|
||||
|
||||
# @example allows Obligation to be risen
|
||||
# rejected_ivar = Ivar.new.fail
|
||||
# raise rejected_ivar
|
||||
def exception(*args)
|
||||
raise 'obligation is not rejected' unless rejected?
|
||||
reason.exception(*args)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @!visibility private
|
||||
def get_arguments_from(opts = {})
|
||||
[*opts.fetch(:args, [])]
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def init_obligation
|
||||
@event = Event.new
|
||||
@value = @reason = nil
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def event
|
||||
@event
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def set_state(success, value, reason)
|
||||
if success
|
||||
@value = value
|
||||
@state = :fulfilled
|
||||
else
|
||||
@reason = reason
|
||||
@state = :rejected
|
||||
end
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def state=(value)
|
||||
synchronize { ns_set_state(value) }
|
||||
end
|
||||
|
||||
# Atomic compare and set operation
|
||||
# State is set to `next_state` only if `current state == expected_current`.
|
||||
#
|
||||
# @param [Symbol] next_state
|
||||
# @param [Symbol] expected_current
|
||||
#
|
||||
# @return [Boolean] true is state is changed, false otherwise
|
||||
#
|
||||
# @!visibility private
|
||||
def compare_and_set_state(next_state, *expected_current)
|
||||
synchronize do
|
||||
if expected_current.include? @state
|
||||
@state = next_state
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Executes the block within mutex if current state is included in expected_states
|
||||
#
|
||||
# @return block value if executed, false otherwise
|
||||
#
|
||||
# @!visibility private
|
||||
def if_state(*expected_states)
|
||||
synchronize do
|
||||
raise ArgumentError.new('no block given') unless block_given?
|
||||
|
||||
if expected_states.include? @state
|
||||
yield
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Am I in the current state?
|
||||
#
|
||||
# @param [Symbol] expected The state to check against
|
||||
# @return [Boolean] true if in the expected state else false
|
||||
#
|
||||
# @!visibility private
|
||||
def ns_check_state?(expected)
|
||||
@state == expected
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def ns_set_state(value)
|
||||
@state = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user