210 lines
7.8 KiB
Ruby
210 lines
7.8 KiB
Ruby
|
#--
|
||
|
#
|
||
|
# Author:: Francis Cianfrocca (gmail: blackhedd)
|
||
|
# Homepage:: http://rubyeventmachine.com
|
||
|
# Date:: 16 Jul 2006
|
||
|
#
|
||
|
# See EventMachine and EventMachine::Connection for documentation and
|
||
|
# usage examples.
|
||
|
#
|
||
|
#----------------------------------------------------------------------------
|
||
|
#
|
||
|
# Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
|
||
|
# Gmail: blackhedd
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of either: 1) the GNU General Public License
|
||
|
# as published by the Free Software Foundation; either version 2 of the
|
||
|
# License, or (at your option) any later version; or 2) Ruby's License.
|
||
|
#
|
||
|
# See the file COPYING for complete licensing information.
|
||
|
#
|
||
|
#---------------------------------------------------------------------------
|
||
|
#
|
||
|
#
|
||
|
|
||
|
module EventMachine
|
||
|
module Deferrable
|
||
|
autoload :Pool, 'em/deferrable/pool'
|
||
|
|
||
|
# Specify a block to be executed if and when the Deferrable object receives
|
||
|
# a status of :succeeded. See #set_deferred_status for more information.
|
||
|
#
|
||
|
# Calling this method on a Deferrable object whose status is not yet known
|
||
|
# will cause the callback block to be stored on an internal list.
|
||
|
# If you call this method on a Deferrable whose status is :succeeded, the
|
||
|
# block will be executed immediately, receiving the parameters given to the
|
||
|
# prior #set_deferred_status call.
|
||
|
#
|
||
|
#--
|
||
|
# If there is no status, add a callback to an internal list.
|
||
|
# If status is succeeded, execute the callback immediately.
|
||
|
# If status is failed, do nothing.
|
||
|
#
|
||
|
def callback &block
|
||
|
return unless block
|
||
|
@deferred_status ||= :unknown
|
||
|
if @deferred_status == :succeeded
|
||
|
block.call(*@deferred_args)
|
||
|
elsif @deferred_status != :failed
|
||
|
@callbacks ||= []
|
||
|
@callbacks.unshift block # << block
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Cancels an outstanding callback to &block if any. Undoes the action of #callback.
|
||
|
#
|
||
|
def cancel_callback block
|
||
|
@callbacks ||= []
|
||
|
@callbacks.delete block
|
||
|
end
|
||
|
|
||
|
# Specify a block to be executed if and when the Deferrable object receives
|
||
|
# a status of :failed. See #set_deferred_status for more information.
|
||
|
#--
|
||
|
# If there is no status, add an errback to an internal list.
|
||
|
# If status is failed, execute the errback immediately.
|
||
|
# If status is succeeded, do nothing.
|
||
|
#
|
||
|
def errback &block
|
||
|
return unless block
|
||
|
@deferred_status ||= :unknown
|
||
|
if @deferred_status == :failed
|
||
|
block.call(*@deferred_args)
|
||
|
elsif @deferred_status != :succeeded
|
||
|
@errbacks ||= []
|
||
|
@errbacks.unshift block # << block
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Cancels an outstanding errback to &block if any. Undoes the action of #errback.
|
||
|
#
|
||
|
def cancel_errback block
|
||
|
@errbacks ||= []
|
||
|
@errbacks.delete block
|
||
|
end
|
||
|
|
||
|
# Sets the "disposition" (status) of the Deferrable object. See also the large set of
|
||
|
# sugarings for this method.
|
||
|
# Note that if you call this method without arguments,
|
||
|
# no arguments will be passed to the callback/errback.
|
||
|
# If the user has coded these with arguments, then the
|
||
|
# user code will throw an argument exception.
|
||
|
# Implementors of deferrable classes <b>must</b>
|
||
|
# document the arguments they will supply to user callbacks.
|
||
|
#
|
||
|
# OBSERVE SOMETHING VERY SPECIAL here: you may call this method even
|
||
|
# on the INSIDE of a callback. This is very useful when a previously-registered
|
||
|
# callback wants to change the parameters that will be passed to subsequently-registered
|
||
|
# ones.
|
||
|
#
|
||
|
# You may give either :succeeded or :failed as the status argument.
|
||
|
#
|
||
|
# If you pass :succeeded, then all of the blocks passed to the object using the #callback
|
||
|
# method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
|
||
|
# passed to the object using #errback will be discarded.
|
||
|
#
|
||
|
# If you pass :failed, then all of the blocks passed to the object using the #errback
|
||
|
# method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
|
||
|
# passed to the object using # callback will be discarded.
|
||
|
#
|
||
|
# If you pass any arguments to #set_deferred_status in addition to the status argument,
|
||
|
# they will be passed as arguments to any callbacks or errbacks that are executed.
|
||
|
# It's your responsibility to ensure that the argument lists specified in your callbacks and
|
||
|
# errbacks match the arguments given in calls to #set_deferred_status, otherwise Ruby will raise
|
||
|
# an ArgumentError.
|
||
|
#
|
||
|
#--
|
||
|
# We're shifting callbacks off and discarding them as we execute them.
|
||
|
# This is valid because by definition callbacks are executed no more than
|
||
|
# once. It also has the magic effect of permitting recursive calls, which
|
||
|
# means that a callback can call #set_deferred_status and change the parameters
|
||
|
# that will be sent to subsequent callbacks down the chain.
|
||
|
#
|
||
|
# Changed @callbacks and @errbacks from push/shift to unshift/pop, per suggestion
|
||
|
# by Kirk Haines, to work around the memory leak bug that still exists in many Ruby
|
||
|
# versions.
|
||
|
#
|
||
|
# Changed 15Sep07: after processing callbacks or errbacks, CLEAR the other set of
|
||
|
# handlers. This gets us a little closer to the behavior of Twisted's "deferred,"
|
||
|
# which only allows status to be set once. Prior to making this change, it was possible
|
||
|
# to "succeed" a Deferrable (triggering its callbacks), and then immediately "fail" it,
|
||
|
# triggering its errbacks! That is clearly undesirable, but it's just as undesirable
|
||
|
# to raise an exception is status is set more than once on a Deferrable. The latter
|
||
|
# behavior would invalidate the idiom of resetting arguments by setting status from
|
||
|
# within a callback or errback, but more seriously it would cause spurious errors
|
||
|
# if a Deferrable was timed out and then an attempt was made to succeed it. See the
|
||
|
# comments under the new method #timeout.
|
||
|
#
|
||
|
def set_deferred_status status, *args
|
||
|
cancel_timeout
|
||
|
@errbacks ||= nil
|
||
|
@callbacks ||= nil
|
||
|
@deferred_status = status
|
||
|
@deferred_args = args
|
||
|
case @deferred_status
|
||
|
when :succeeded
|
||
|
if @callbacks
|
||
|
while cb = @callbacks.pop
|
||
|
cb.call(*@deferred_args)
|
||
|
end
|
||
|
end
|
||
|
@errbacks.clear if @errbacks
|
||
|
when :failed
|
||
|
if @errbacks
|
||
|
while eb = @errbacks.pop
|
||
|
eb.call(*@deferred_args)
|
||
|
end
|
||
|
end
|
||
|
@callbacks.clear if @callbacks
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
# Setting a timeout on a Deferrable causes it to go into the failed state after
|
||
|
# the Timeout expires (passing no arguments to the object's errbacks).
|
||
|
# Setting the status at any time prior to a call to the expiration of the timeout
|
||
|
# will cause the timer to be cancelled.
|
||
|
def timeout seconds, *args
|
||
|
cancel_timeout
|
||
|
me = self
|
||
|
@deferred_timeout = EventMachine::Timer.new(seconds) {me.fail(*args)}
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Cancels an outstanding timeout if any. Undoes the action of #timeout.
|
||
|
#
|
||
|
def cancel_timeout
|
||
|
@deferred_timeout ||= nil
|
||
|
if @deferred_timeout
|
||
|
@deferred_timeout.cancel
|
||
|
@deferred_timeout = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
# Sugar for set_deferred_status(:succeeded, ...)
|
||
|
#
|
||
|
def succeed *args
|
||
|
set_deferred_status :succeeded, *args
|
||
|
end
|
||
|
alias set_deferred_success succeed
|
||
|
|
||
|
# Sugar for set_deferred_status(:failed, ...)
|
||
|
#
|
||
|
def fail *args
|
||
|
set_deferred_status :failed, *args
|
||
|
end
|
||
|
alias set_deferred_failure fail
|
||
|
end
|
||
|
|
||
|
|
||
|
# DefaultDeferrable is an otherwise empty class that includes Deferrable.
|
||
|
# This is very useful when you just need to return a Deferrable object
|
||
|
# as a way of communicating deferred status to some other part of a program.
|
||
|
class DefaultDeferrable
|
||
|
include Deferrable
|
||
|
end
|
||
|
end
|