require 'concurrent/concern/deprecation'
module Concurrent
# TODO (pitr-ch 27-Mar-2016): cooperation with mutex, condition, select etc?
# TODO (pitr-ch 10-Dec-2018): integrate with enumerator?
# token.cancelable(array.each_with_index).each do |v, i|
# # stops iterating when cancelled
# end
# token.cancelable(array).each_with_index do |v, i|
# # stops iterating when cancelled
# end
# The Cancellation abstraction provides cooperative cancellation.
#
# The standard methods `Thread#raise` of `Thread#kill` available in Ruby
# are very dangerous (see linked the blog posts bellow).
# Therefore concurrent-ruby provides an alternative.
# *
# *
# *
#
# It provides an object which represents a task which can be executed,
# the task has to get the reference to the object and periodically cooperatively check that it is not cancelled.
# Good practices to make tasks cancellable:
# * check cancellation every cycle of a loop which does significant work,
# * do all blocking actions in a loop with a timeout then on timeout check cancellation
# and if ok block again with the timeout
#
# The idea was inspired by
# @!macro warn.edge
#
# {include:file:docs-source/cancellation.out.md}
class Cancellation < Synchronization::Object
safe_initialization!
# Create Cancellation which will cancel itself in given time
#
# @!macro promises.param.intended_time
# @return [Cancellation]
def self.timeout(intended_time)
new Concurrent::Promises.schedule(intended_time)
end
# Creates the cancellation object.
#
# @param [Promises::Future, Promises::Event] origin of the cancellation.
# When it is resolved the cancellation is canceled.
# @example
# cancellation, origin = Concurrent::Cancellation.new
# @see #to_ary
def initialize(origin = Promises.resolvable_event)
super()
@Origin = origin
end
# Allow to multi-assign the Cancellation object
# @return [Array(Cancellation, Promises::Future), Array(Cancellation, Promises::Event)]
# @example
# cancellation = Concurrent::Cancellation.new
# cancellation, origin = Concurrent::Cancellation.new
def to_ary
[self, @Origin]
end
# The event or future which is the origin of the cancellation
# @return [Promises::Future, Promises::Event]
def origin
@Origin
end
# Is the cancellation cancelled?
# Respective, was the origin of the cancellation resolved.
# @return [true, false]
def canceled?
@Origin.resolved?
end
# Raise error when cancelled
# @param [#exception] error to be risen
# @raise the error
# @return [self]
def check!(error = CancelledOperationError)
raise error if canceled?
self
end
# Creates a new Cancellation which is cancelled when first
# of the supplied cancellations or self is cancelled.
#
# @param [Cancellation] cancellations to combine
# @return [Cancellation] new cancellation
def join(*cancellations)
Cancellation.new Promises.any_event(*[@Origin, *cancellations.map(&:origin)])
end
# Short string representation.
# @return [String]
def to_s
format '%s %s>', super[0..-2], canceled? ? 'canceled' : 'pending'
end
alias_method :inspect, :to_s
end
end