summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb
diff options
context:
space:
mode:
authorBenjamin Sanders <ben@Benjamins-MacBook-Pro.local>2025-10-11 10:34:24 -0400
committerBenjamin Sanders <ben@Benjamins-MacBook-Pro.local>2025-10-11 10:34:24 -0400
commit5571dc766e2143e39762ff0b47c41d1c2e154f4c (patch)
treef4f124d314a7e76c04484515aa0fd1f2e271a601 /vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb
parent359f3309c97fc894b63e0a295c76ab298504d635 (diff)
Vendor bundled gems for deployment
Diffstat (limited to 'vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb')
-rw-r--r--vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb304
1 files changed, 304 insertions, 0 deletions
diff --git a/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb
new file mode 100644
index 0000000..926535f
--- /dev/null
+++ b/vendor/bundle/ruby/3.4.0/gems/eventmachine-1.2.7/lib/em/completion.rb
@@ -0,0 +1,304 @@
+# = EM::Completion
+#
+# A completion is a callback container for various states of completion. In
+# its most basic form it has a start state and a finish state.
+#
+# This implementation includes some hold-back from the EM::Deferrable
+# interface in order to be compatible - but it has a much cleaner
+# implementation.
+#
+# In general it is preferred that this implementation be used as a state
+# callback container than EM::DefaultDeferrable or other classes including
+# EM::Deferrable. This is because it is generally more sane to keep this level
+# of state in a dedicated state-back container. This generally leads to more
+# malleable interfaces and software designs, as well as eradicating nasty bugs
+# that result from abstraction leakage.
+#
+# == Basic Usage
+#
+# As already mentioned, the basic usage of a Completion is simply for its two
+# final states, :succeeded and :failed.
+#
+# An asynchronous operation will complete at some future point in time, and
+# users often want to react to this event. API authors will want to expose
+# some common interface to react to these events.
+#
+# In the following example, the user wants to know when a short lived
+# connection has completed its exchange with the remote server. The simple
+# protocol just waits for an ack to its message.
+#
+# class Protocol < EM::Connection
+# include EM::P::LineText2
+#
+# def initialize(message, completion)
+# @message, @completion = message, completion
+# @completion.completion { close_connection }
+# @completion.timeout(1, :timeout)
+# end
+#
+# def post_init
+# send_data(@message)
+# end
+#
+# def receive_line(line)
+# case line
+# when /ACK/i
+# @completion.succeed line
+# when /ERR/i
+# @completion.fail :error, line
+# else
+# @completion.fail :unknown, line
+# end
+# end
+#
+# def unbind
+# @completion.fail :disconnected unless @completion.completed?
+# end
+# end
+#
+# class API
+# attr_reader :host, :port
+#
+# def initialize(host = 'example.org', port = 8000)
+# @host, @port = host, port
+# end
+#
+# def request(message)
+# completion = EM::Deferrable::Completion.new
+# EM.connect(host, port, Protocol, message, completion)
+# completion
+# end
+# end
+#
+# api = API.new
+# completion = api.request('stuff')
+# completion.callback do |line|
+# puts "API responded with: #{line}"
+# end
+# completion.errback do |type, line|
+# case type
+# when :error
+# puts "API error: #{line}"
+# when :unknown
+# puts "API returned unknown response: #{line}"
+# when :disconnected
+# puts "API server disconnected prematurely"
+# when :timeout
+# puts "API server did not respond in a timely fashion"
+# end
+# end
+#
+# == Advanced Usage
+#
+# This completion implementation also supports more state callbacks and
+# arbitrary states (unlike the original Deferrable API). This allows for basic
+# stateful process encapsulation. One might use this to setup state callbacks
+# for various states in an exchange like in the basic usage example, except
+# where the applicaiton could be made to react to "connected" and
+# "disconnected" states additionally.
+#
+# class Protocol < EM::Connection
+# def initialize(completion)
+# @response = []
+# @completion = completion
+# @completion.stateback(:disconnected) do
+# @completion.succeed @response.join
+# end
+# end
+#
+# def connection_completed
+# @host, @port = Socket.unpack_sockaddr_in get_peername
+# @completion.change_state(:connected, @host, @port)
+# send_data("GET http://example.org/ HTTP/1.0\r\n\r\n")
+# end
+#
+# def receive_data(data)
+# @response << data
+# end
+#
+# def unbind
+# @completion.change_state(:disconnected, @host, @port)
+# end
+# end
+#
+# completion = EM::Deferrable::Completion.new
+# completion.stateback(:connected) do |host, port|
+# puts "Connected to #{host}:#{port}"
+# end
+# completion.stateback(:disconnected) do |host, port|
+# puts "Disconnected from #{host}:#{port}"
+# end
+# completion.callback do |response|
+# puts response
+# end
+#
+# EM.connect('example.org', 80, Protocol, completion)
+#
+# == Timeout
+#
+# The Completion also has a timeout. The timeout is global and is not aware of
+# states apart from completion states. The timeout is only engaged if #timeout
+# is called, and it will call fail if it is reached.
+#
+# == Completion states
+#
+# By default there are two completion states, :succeeded and :failed. These
+# states can be modified by subclassing and overrding the #completion_states
+# method. Completion states are special, in that callbacks for all completion
+# states are explcitly cleared when a completion state is entered. This
+# prevents errors that could arise from accidental unterminated timeouts, and
+# other such user errors.
+#
+# == Other notes
+#
+# Several APIs have been carried over from EM::Deferrable for compatibility
+# reasons during a transitionary period. Specifically cancel_errback and
+# cancel_callback are implemented, but their usage is to be strongly
+# discouraged. Due to the already complex nature of reaction systems, dynamic
+# callback deletion only makes the problem much worse. It is always better to
+# add correct conditionals to the callback code, or use more states, than to
+# address such implementaiton issues with conditional callbacks.
+
+module EventMachine
+
+ class Completion
+ # This is totally not used (re-implemented), it's here in case people check
+ # for kind_of?
+ include EventMachine::Deferrable
+
+ attr_reader :state, :value
+
+ def initialize
+ @state = :unknown
+ @callbacks = Hash.new { |h,k| h[k] = [] }
+ @value = []
+ @timeout_timer = nil
+ end
+
+ # Enter the :succeeded state, setting the result value if given.
+ def succeed(*args)
+ change_state(:succeeded, *args)
+ end
+ # The old EM method:
+ alias set_deferred_success succeed
+
+ # Enter the :failed state, setting the result value if given.
+ def fail(*args)
+ change_state(:failed, *args)
+ end
+ # The old EM method:
+ alias set_deferred_failure fail
+
+ # Statebacks are called when you enter (or are in) the named state.
+ def stateback(state, *a, &b)
+ # The following is quite unfortunate special casing for :completed
+ # statebacks, but it's a necessary evil for latent completion
+ # definitions.
+
+ if :completed == state || !completed? || @state == state
+ @callbacks[state] << EM::Callback(*a, &b)
+ end
+ execute_callbacks
+ self
+ end
+
+ # Callbacks are called when you enter (or are in) a :succeeded state.
+ def callback(*a, &b)
+ stateback(:succeeded, *a, &b)
+ end
+
+ # Errbacks are called when you enter (or are in) a :failed state.
+ def errback(*a, &b)
+ stateback(:failed, *a, &b)
+ end
+
+ # Completions are called when you enter (or are in) either a :failed or a
+ # :succeeded state. They are stored as a special (reserved) state called
+ # :completed.
+ def completion(*a, &b)
+ stateback(:completed, *a, &b)
+ end
+
+ # Enter a new state, setting the result value if given. If the state is one
+ # of :succeeded or :failed, then :completed callbacks will also be called.
+ def change_state(state, *args)
+ @value = args
+ @state = state
+
+ EM.schedule { execute_callbacks }
+ end
+
+ # The old EM method:
+ alias set_deferred_status change_state
+
+ # Indicates that we've reached some kind of completion state, by default
+ # this is :succeeded or :failed. Due to these semantics, the :completed
+ # state is reserved for internal use.
+ def completed?
+ completion_states.any? { |s| state == s }
+ end
+
+ # Completion states simply returns a list of completion states, by default
+ # this is :succeeded and :failed.
+ def completion_states
+ [:succeeded, :failed]
+ end
+
+ # Schedule a time which if passes before we enter a completion state, this
+ # deferrable will be failed with the given arguments.
+ def timeout(time, *args)
+ cancel_timeout
+ @timeout_timer = EM::Timer.new(time) do
+ fail(*args) unless completed?
+ end
+ end
+
+ # Disable the timeout
+ def cancel_timeout
+ if @timeout_timer
+ @timeout_timer.cancel
+ @timeout_timer = nil
+ end
+ end
+
+ # Remove an errback. N.B. Some errbacks cannot be deleted. Usage is NOT
+ # recommended, this is an anti-pattern.
+ def cancel_errback(*a, &b)
+ @callbacks[:failed].delete(EM::Callback(*a, &b))
+ end
+
+ # Remove a callback. N.B. Some callbacks cannot be deleted. Usage is NOT
+ # recommended, this is an anti-pattern.
+ def cancel_callback(*a, &b)
+ @callbacks[:succeeded].delete(EM::Callback(*a, &b))
+ end
+
+ private
+ # Execute all callbacks for the current state. If in a completed state, then
+ # call any statebacks associated with the completed state.
+ def execute_callbacks
+ execute_state_callbacks(state)
+ if completed?
+ execute_state_callbacks(:completed)
+ clear_dead_callbacks
+ cancel_timeout
+ end
+ end
+
+ # Iterate all callbacks for a given state, and remove then call them.
+ def execute_state_callbacks(state)
+ while callback = @callbacks[state].shift
+ callback.call(*value)
+ end
+ end
+
+ # If we enter a completion state, clear other completion states after all
+ # callback chains are completed. This means that operation specific
+ # callbacks can't be dual-called, which is most common user error.
+ def clear_dead_callbacks
+ completion_states.each do |state|
+ @callbacks[state].clear
+ end
+ end
+ end
+end