rf-web/vendor/bundle/gems/eventmachine-1.2.7/docs/old/DEFERRABLES

247 lines
11 KiB
Plaintext
Raw Normal View History

2019-10-21 08:18:17 +00:00
EventMachine (EM) adds two different formalisms for lightweight concurrency
to the Ruby programmer's toolbox: spawned processes and deferrables. This
note will show you how to use deferrables. For more information, see the
separate document LIGHTWEIGHT_CONCURRENCY.
=== What are Deferrables?
EventMachine's Deferrable borrows heavily from the "deferred" object in
Python's "Twisted" event-handling framework. Here's a minimal example that
illustrates Deferrable:
require 'eventmachine'
class MyClass
include EM::Deferrable
def print_value x
puts "MyClass instance received #{x}"
end
end
EM.run {
df = MyClass.new
df.callback {|x|
df.print_value(x)
EM.stop
}
EM::Timer.new(2) {
df.set_deferred_status :succeeded, 100
}
}
This program will spin for two seconds, print out the string "MyClass
instance received 100" and then exit. The Deferrable pattern relies on
an unusual metaphor that may be unfamiliar to you, unless you've used
Python's Twisted. You may need to read the following material through
more than once before you get the idea.
EventMachine::Deferrable is simply a Ruby Module that you can include
in your own classes. (There also is a class named
EventMachine::DefaultDeferrable for when you want to create one without
including it in code of your own.)
An object that includes EventMachine::Deferrable is like any other Ruby
object: it can be created whenever you want, returned from your functions,
or passed as an argument to other functions.
The Deferrable pattern allows you to specify any number of Ruby code
blocks (callbacks or errbacks) that will be executed at some future time
when the status of the Deferrable object changes.
How might that be useful? Well, imagine that you're implementing an HTTP
server, but you need to make a call to some other server in order to fulfill
a client request.
When you receive a request from one of your clients, you can create and
return a Deferrable object. Some other section of your program can add a
callback to the Deferrable that will cause the client's request to be
fulfilled. Simultaneously, you initiate an event-driven or threaded client
request to some different server. And then your EM program will continue to
process other events and service other client requests.
When your client request to the other server completes some time later, you
will call the #set_deferred_status method on the Deferrable object, passing
either a success or failure status, and an arbitrary number of parameters
(which might include the data you received from the other server).
At that point, the status of the Deferrable object becomes known, and its
callback or errback methods are immediately executed. Callbacks and errbacks
are code blocks that are attached to Deferrable objects at any time through
the methods #callback and #errback.
The deep beauty of this pattern is that it decouples the disposition of one
operation (such as a client request to an outboard server) from the
subsequent operations that depend on that disposition (which may include
responding to a different client or any other operation).
The code which invokes the deferred operation (that will eventually result
in a success or failure status together with associated data) is completely
separate from the code which depends on that status and data. This achieves
one of the primary goals for which threading is typically used in
sophisticated applications, with none of the nondeterminacy or debugging
difficulties of threads.
As soon as the deferred status of a Deferrable becomes known by way of a call
to #set_deferred_status, the Deferrable will IMMEDIATELY execute all of its
callbacks or errbacks in the order in which they were added to the Deferrable.
Callbacks and errbacks can be added to a Deferrable object at any time, not
just when the object is created. They can even be added after the status of
the object has been determined! (In this case, they will be executed
immediately when they are added.)
A call to Deferrable#set_deferred_status takes :succeeded or :failed as its
first argument. (This determines whether the object will call its callbacks
or its errbacks.) #set_deferred_status also takes zero or more additional
parameters, that will in turn be passed as parameters to the callbacks or
errbacks.
In general, you can only call #set_deferred_status ONCE on a Deferrable
object. A call to #set_deferred_status will not return until all of the
associated callbacks or errbacks have been called. If you add callbacks or
errbacks AFTER making a call to #set_deferred_status, those additional
callbacks or errbacks will execute IMMEDIATELY. Any given callback or
errback will be executed AT MOST once.
It's possible to call #set_deferred_status AGAIN, during the execution a
callback or errback. This makes it possible to change the parameters which
will be sent to the callbacks or errbacks farther down the chain, enabling
some extremely elegant use-cases. You can transform the data returned from
a deferred operation in arbitrary ways as needed by subsequent users, without
changing any of the code that generated the original data.
A call to #set_deferred_status will not return until all of the associated
callbacks or errbacks have been called. If you add callbacks or errbacks
AFTER making a call to #set_deferred_status, those additional callbacks or
errbacks will execute IMMEDIATELY.
Let's look at some more sample code. It turns out that many of the internal
protocol implementations in the EventMachine package rely on Deferrable. One
of these is EM::Protocols::HttpClient.
To make an evented HTTP request, use the module function
EM::Protocols::HttpClient#request, which returns a Deferrable object.
Here's how:
require 'eventmachine'
EM.run {
df = EM::Protocols::HttpClient.request( :host=>"www.example.com",
:request=>"/index.html" )
df.callback {|response|
puts "Succeeded: #{response[:content]}"
EM.stop
}
df.errback {|response|
puts "ERROR: #{response[:status]}"
EM.stop
}
}
(See the documentation of EventMachine::Protocols::HttpClient for information
on the object returned by #request.)
In this code, we make a call to HttpClient#request, which immediately returns
a Deferrable object. In the background, an HTTP client request is being made
to www.example.com, although your code will continue to run concurrently.
At some future point, the HTTP client request will complete, and the code in
EM::Protocols::HttpClient will process either a valid HTTP response (including
returned content), or an error.
At that point, EM::Protocols::HttpClient will call
EM::Deferrable#set_deferred_status on the Deferrable object that was returned
to your program, as the return value from EM::Protocols::HttpClient.request.
You don't have to do anything to make this happen. All you have to do is tell
the Deferrable what to do in case of either success, failure, or both.
In our code sample, we set one callback and one errback. The former will be
called if the HTTP call succeeds, and the latter if it fails. (For
simplicity, we have both of them calling EM#stop to end the program, although
real programs would be very unlikely to do this.)
Setting callbacks and errbacks is optional. They are handlers to defined
events in the lifecycle of the Deferrable event. It's not an error if you
fail to set either a callback, an errback, or both. But of course your
program will then fail to receive those notifications.
If through some bug it turns out that #set_deferred_status is never called
on a Deferrable object, then that object's callbacks or errbacks will NEVER
be called. It's also possible to set a timeout on a Deferrable. If the
timeout elapses before any other call to #set_deferred_status, the Deferrable
object will behave as is you had called set_deferred_status(:failed) on it.
Now let's modify the example to illustrate some additional points:
require 'eventmachine'
EM.run {
df = EM::Protocols::HttpClient.request( :host=>"www.example.com",
:request=>"/index.html" )
df.callback {|response|
df.set_deferred_status :succeeded, response[:content]
}
df.callback {|string|
puts "Succeeded: #{string}"
EM.stop
}
df.errback {|response|
puts "ERROR: #{response[:status]}"
EM.stop
}
}
Just for the sake of illustration, we've now set two callbacks instead of
one. If the deferrable operation (the HTTP client-request) succeeds, then
both of the callbacks will be executed in order.
But notice that we've also made our own call to #set_deferred_status in the
first callback. This isn't required, because the HttpClient implementation
already made a call to #set_deferred_status. (Otherwise, of course, the
callback would not be executing.)
But we used #set_deferred_status in the first callback in order to change the
parameters that will be sent to subsequent callbacks in the chain. In this
way, you can construct powerful sequences of layered functionality. If you
want, you can even change the status of the Deferrable from :succeeded to
:failed, which would abort the chain of callback calls, and invoke the chain
of errbacks instead.
Now of course it's somewhat trivial to define two callbacks in the same
method, even with the parameter-changing effect we just described. It would
be much more interesting to pass the Deferrable to some other function (for
example, a function defined in another module or a different gem), that would
in turn add callbacks and/or errbacks of its own. That would illustrate the
true power of the Deferrable pattern: to isolate the HTTP client-request
from other functions that use the data that it returns without caring where
those data came from.
Remember that you can add a callback or an errback to a Deferrable at any
point in time, regardless of whether the status of the deferred operation is
known (more precisely, regardless of when #set_deferred_status is called on
the object). Even hours or days later.
When you add a callback or errback to a Deferrable object on which
#set_deferred_status has not yet been called, the callback/errback is queued
up for future execution, inside the Deferrable object. When you add a
callback or errback to a Deferrable on which #set_deferred_status has
already been called, the callback/errback will be executed immediately.
Your code doesn't have to worry about the ordering, and there are no timing
issues, as there would be with a threaded approach.
For more information on Deferrables and their typical usage patterns, look
in the EM unit tests. There are also quite a few sugarings (including
EM::Deferrable#future) that make typical Deferrable usages syntactically
easier to work with.