The second problem lies in the fact that objects returned by the broker are
CAO types. This is far from optimal. The client must explicitly release the
object when done with the object calling Dispose. If it fails to do so, the
object is returned to the pool only when the object Lease time is expired.
Properly designed pooled objects don’t keep any client specific state across
calls, thus for this reason it would be highly desirable to pass back to clients
SAO SingleCall object types. This optimize the pool management, not letting
the client hold a reference to a pooled object longer than necessary.
Activation Transparency and
SingleCall Behavior via a Custom Channel Sink
Remoting behavior can be customized in two ways: defining a custom proxy
or inserting a custom sink in the default sink chain. The custom proxy
approach is not so compelling, since you cannot insert a custom proxy transparently
(the client must explicitly create it) and anything you can do with a custom
proxy can actually be done using a custom sink.
We will then opt for a custom sink to enable transparent activation and SingleCall
behavior for our pooled objects. The solution we will come out with won’t work
for Client Activated objects, but as I said, CAO are not a desirable option
when working with pooled objects.
Anyway, note you still can get CAO behaving objects when accessing the Pool
Manager explicitly.
Custom Sinks and
Custom Sink Providers
Setting up a custom sink requires the development of two classes: the custom
sink class itself and a custom sink provider class. The latter has
to be registered into the remoting infrastructure. The sole role of the sink
provider is to return to the remoting infrastructure an instance of its associated
custom sink when required.
Here is a remote configuration file where a custom sink provider is registered
before the formatter
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="1234">
<serverProviders>
<provider type="Sink.MySinkProviderBeforeFormatter, Sink" />
<formatter ref="binary" />
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Client sinks providers must implement the IClientChannelSinkProvider
and server sinks must implement the IServerChannelSinkProvider.
While setting up the channel, remoting reads the channel configuration and creates
a linked chain of sinks accordingly.
All registered sinks will receive the message payload and given a chance to
modify it, log it, or whatever they want to, before forwarding it to the next
sink.
Note that sinks are somewhat logically equivalent to HTTP modules. There is
still a great difference. HTTP modules are contacted via a callback mechanism
(they are not in the HTTP Request processing call stack). On the contrary, custom
sinks are right in the call stack chain and are given the responsibility to
forward the message when they are done.
Setting up the sink chain is a two step process. In the first step, remoting
set up a linked list of sink providers. It calls the Next method of all
registered providers passing an instance of the next sink provider in the chain.
The provider is required to memorize this object in an instance level variable
since it is required for the following step.
In the second step remoting calls the CreateSink method of the first
sink provider in the chain. In this method the provider must return an instance
of the custom sink it acts as provider. Since the sinks must be chained as well,
the custom sink must be passed an instance of its next sink as parameter constructor.
To do that the provider must call the CreateSink method of the next provider
whose instance was acquired in the previous step. Consequently, each provider
calls the CreateSink methods of its next provider till all the provider chain
is walked up.
Here is a code snippet of a standard implementation of the CreateSink method.
Private Function CreateSink(ByVal channel As IChannelReceiver) _
As IServerChannelSink
Implements IServerChannelSinkProvider.CreateSink
Dim nextSink As IServerChannelSink = Nothing
i_next is the next provider in the provider chain
If Not i_next Is Nothing Then
nextSink = i_next.CreateSink(channel)
End If
Return New MySinkBeforeFormatter(nextSink)
End Function
Implementing a custom sink is slightly more
complicated. There are actually three different interfaces to implement, depending
on where the custom sink has to be placed. On the client side the sink must
implement the IMessageSink interface if it has to be placed before the
formatter and the IClientChannelSink if it has to be place after the
formatter. A server side custom sink must implement the IServerChannelSink,
no matter if it is placed before or after the formatter.
In all the aforementioned cases the skeleton of a sink provider and of a custom
sink is boiler plate code. Once you have the proper class template for each
kind of provider and sink you can copy and past the template and go straight
to the method where you can intercept the message and do the real work.
In this article we will concentrate on a custom sink placed before the formatter.
This is where we will play with the message to make the process of getting a
reference to a pooled object transparent to the client.
You may find samples of any kind of sink and sink provider in the code that
comes with this article.