Passing objects by reference in Indigo

I'll be honest. The feature I really want to talk about in this part of Indigo is instancing and Duplex channels, but I'll do that soon. I have heard concerns about Indigo's support for distributed objects. I hope to address those concerns as well as provide some background for discussing Duplex.

I should begin by saying that passing object references remotely is bad. Don't ever do this. Shame on you for reading this blog entry. Passing objects by reference kills scalability. Passing objects by reference introduces coupling and therefore makes your application more fragile. "Ryan" passed objects by reference once--that very day his Smartphone met an unfortunate end, he got a speeding ticket, and dandelions took over his front yard. You get the idea.

Of course I'm mostly kidding, except for the part about losing scalability and having fragile apps. Now that the Best-Practice-Police are satisfied, let's see what kind of trouble we can get into.

Reference Passing in .NET Remoting

Using .NET Remoting to pass references is simple, automatic, and completely transparent to the caller. All I need to do is create an object whose type derives from MarshalByRefObject. I can then call a method on a proxy and pass the object by reference:

 proxy.Method(myMarshalByRefObject);
myMarshalByRefObject.Callback("local?");

The implementer of Method() can take the object and start using it:

 void Method(MyMarshalByRefObject myMarshalByRefObject)
{
    myMarshalByRefObject.Callback("remote?");
}

Because we passed a reference to Method(), both Callback()s execute on the same CLR object. .NET Remoting does a lot of work to make this seamless, and it is pretty amazingly cool. Of course, there is a downside--it is so easy you might even do it accidentally. (Did you know System.Uri is MarshalByRefObject? Oops.)

Reference Passing in Indigo

This could make an interesting out-of-context sound-bite: "If you want to do bad things, Indigo makes your job harder." Indigo does require that you be explicit when you want to pass an object by reference. The server side is not too bad--just add this attribute to your service class:

 [ServiceBehavior(InstanceMode=InstanceMode.SharedSession)]

This turns on all the server-side infrastructure goo that lets you connect new channels to objects that already exist.

On the client, a serializable object reference in Indigo looks like an EndpointAddress. This is true both in code and on the wire. Typically the EndpointAddress will be the service's EndpointAddress plus one or more InstanceHeaders. Indigo provides an explicit method to convert from a proxy to a serializable EndpointAddress:

 EndpointAddress address = ((IProxyChannel)proxy).ResolveInstance();

This is a lossy transformation--type/contract information is not preserved. Indigo also provides an explicit method to convert from a deserialized EndpointAddress to a proxy:

 IHello proxy = ChannelFactory.CreateChannel<IHello>(address);

Here is a sample that demonstrates this conversion. I traded realism for simplicity, so you have to use your imagination a bit. The SharedSessionCounter has a name and keeps a running total. The sample creates two of these counters, A and B, and always increments A by 1 and B by 10 to illustrate what is happening. Initially it creates the two objects, then pretends to pass both by reference as EndpointAddresses, then connects a new channel to each object.

Casting

Suppose I have a proxy of type IHello, and the service also implements IHiThere. How do I cast the IHello to IHiThere? DON'T! I would not recommend this, since the client would have an implicit dependency on the internal implementation of the service. Instead, be explicit and have your service implement a third contract that combines the other two:

 [ServiceContract]
interface IGreeting : IHello, IHiThere
{
}

Then you can create an IGreeting channel and do casting (if needed) locally.

If for some reason you really must connect to the same instance using a different contract, you can call CreateChannel again with a different channel type. Note, though, that if something goes wrong here you will only find out by sending a message, and even then it will be difficult to find out programmatically.

Lifetime

In books about distributed object technologies, lifetime management generally takes at least a chapter to explain. I'm sure Indigo will be no exception. However, here is an attempt to summarize how instances shut down when using SharedSession with a small collection of haiku:

 Once all the channels
Go away, the timer starts.
Connect soon or lose.

When timer expires,
Ask extensibility.
Can this instance live?

Now that all agree,
Farewell, my little object.
Mizu no Oto.
  • Caveat 1: If new work comes in before the object goes away, the process starts over.
  • Caveat 2: If someone explicitly calls Close(), BeginClose() or Abort(), the ServiceSite starts closing immediately.
  • Caveat 3: There is a reason I was not an English major.

By the way, in case the InstanceMode value names don't make sense, they have to do with the lifetime of the instance. So PerCall means the instance lasts as long as the call. PrivateSession means the instance is not shareable and lasts as long as the sessionful channel. SharedSession means the instance is shareable and lasts as long as there are sessionful channels connected. And of course, Singletons last "forever", for sufficiently small values of forever.

Alternate EndpointAddress

Suppose I have a service that listens on multiple addresses. If you were watching closely, perhaps it bothered you that ResolveInstance returns just one address. Which one is it? What about the others?

Well, it isn't pretty. IProxyChannel has a collection of headers for exactly this purpose. Here's a method you can use to make it easier:

 EndpointAddress GetInstanceAddressForEndpoint(IProxyChannel proxy, EndpointAddress endpoint)
{
    // Populate the headers
    proxy.ResolveInstance();

    // Build the address:
    EndpointAddressBuilder builder = new EndpointAddressBuilder(endpoint);
    builder.InstanceHeaders.AddRange(proxy.InstanceHeaders);

    // This is the new address:
    EndpointAddress instanceAtEndpoint = builder.ToEndpointAddress();
    return instanceAtEndpoint;
}

Here is the previous sample, modified to create the initial channels over TCP/IP and then connect the second channels using Named Pipes. It uses an overload of the method above to manage this binding change. I tried to minimize changes, so looking at a diff between this and the previous sample may be helpful.

Interoperability

Note that ResolveInstance uses a protocol that is not meant to be particularly interoperable. I doubt it will ever be in a WS-* spec. That said, it should not be too difficult to write an interface or WSDL that expresses this protocol. Of course, I haven't tried that yet.

More than you ever wanted to know about reference passing in Indigo...


This posting is provided "AS IS" with no warranties, and confers no rights.