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