Proxying Objects That Cant Be Distributed
Credit: James Edward Gray II
Problem
You want to allow classes to connect to your DRb server, without giving the server access to the class definition. Perhaps youve given clients an API to implement, and you don want to make everyone send you the source to their implementations just so they can connect to the server.
…OR…
You have some code that is tied to local resources: database connections, log files, or even just the closure aspect of Rubys blocks. You want this code to interact with a DRb server, but it must be run locally.
…OR…
You want to send an object to a DRb server, perhaps as a parameter to a method; but you want the server to notice changes to that object as your local code modifies it.
Solution
Rather than sending an object to the server, you can ask DRb to send a proxy instead. When the server acts on the proxy, a description of the act will be sent across the network. The client end will actually perform the action. In effect, youve partially switched the roles of the client and the server.
You can set up a proxy in two simple steps. First, make sure your client code includes the following line before it interacts with any server objects:
DRb.start_service # The client needs to be a DRb service too.
Thats generally just a good habit to get into with DRb client code, because it allows DRb to magically support some constructs (like Rubys blocks) by sending a proxy object when necessary. If you e intentionally trying to send a proxy, it becomes essential.
As long as your client is a DRb service of its own, you can proxy all objects made from a specific class or individual objects by including the DRbUndumped module:
class MyLocalClass include DRbUndumped # The magic line. All objects of this type are proxied. # … end # … OR … my_local_object.extend DRbUndumped # Proxy just this object.
Discussion
Under normal circumstances, DRb is very simple. A method call is packaged up (using Marshal) as a target object, method name, and some arguments. The resulting object is sent over the wire to the server, where its executed. The important thing to notice is that the server receives copies of the original arguments.
The server unmarshals the data, invokes the method, packages the result, and sends it back. Again, the result objects are copied to the client.
But that process doesn always work. Perhaps the server needs to pass a code block into a method call. Rubys blocks cannot be serialized. DRb notices this special case and sends a proxy object instead. As the server interacts with the proxy, the calls are bundled up and sent back to you, just as described above, so everything just works.
But DRb can magically notice all cases where copying is harmful. Thats why you need DRbUndumped. By extending an object with DRbUndumped, you can force DRb to send a proxy object instead of the real object, and ensure that your code stays local.
If all this sounds confusing, a simple example will probably clear it right up. Lets code up a trivial hello server:
#!/usr/bin/ruby # hello_server.rb require drb # a simple greeter class class HelloService def hello(in_stream, out_stream) out_stream.puts What is your name? name = in_stream.gets.strip out_stream.puts "Hello #{name}." end end # start up DRb with URI and object to share DRb.start_service(druby://localhost:61676, HelloService.new) DRb.thread.join # wait on DRb thread to exit…
Now we try connecting with a simple client:
#!/usr/bin/ruby # hello_client.rb require drb # fetch service object and ask it to greet us… hello_service = DRbObject.new_with_uri(druby://localhost:61676) hello_service.hello($stdin, $stdout)
Unfortunately, that yields an error message. Obviously, $stdin and $stdout are local resources that won be available from the remote service. We need to pass them by proxy to get this working:
#!/usr/bin/ruby # hello_client2.rb require drb DRb.start_service # make sure client can serve proxy objects… # and request that the streams be proxied $stdin.extend DRbUndumped $stdout.extend DRbUndumped # fetch service object and ask it to greet us… hello_service = DRbObject.new_with_uri(druby://localhost:61676) hello_service.hello($stdin, $stdout)
With that client, DRb has remote access to the streams (through the proxy objects) and can read and write them as needed.
See Also
- Recipe 16.10, "Sharing a Hash Between Any Number of Computers"
- Eric Hodels "Introduction to DRb" covers DRbUndumped (http://segment7.net/projects/ruby/drb/introduction.html)
- The DRb presentation by Mark Volkmann in the "Why Ruby?" repository at http://rubyforge.org/docman/view.php/251/216/DistributedRuby.pdf has some material on DRbUndumped
Категории