Поделиться через


Connection Pooling in the WCF LOB ASDK based adapters.

I was recently contacted by a customer using the SAP Adapter. They were using the Adapter directly via a WCF proxy (i.e., without using BizTalk) from within an ASP.NET application. Their complaint was that the throughput was very low. On investigating further, I found that the reason was due to connections being opened and closed, as well as metadata being retrieved from SAP, for every call. All this in spite of the binding property "EnableConnectionPooling" being true. That's strange you might think, but not once you know the details of how the ASDK functions ...

In the WCF LOB ASDK, there was a need for maintaining a pool of open connections to the LOB, since opening connections to LOB systems is usually expensive. When a connection is required by a channel to a particular LOB server (as determined by the URI), with a specific set of credentials, then the appropriate connection is picked up from the pool and used; when the channel is closed, the connection is returned back to the pool.

In the current ASDK design,  the connection pools are maintained at the channel factory instance. (The exact reasons of why this design was chosen - topic of another blog post). What this means is that if you have two channel factory instances, then each channel factory has its own set of connection pools. i.e., for a server A, each channel factory will have one connection pool for this server. Connections from the pools in one channel factory cannot be used by another channel factory.

Another set of objects which are cached are metadata objects. The adapters, at runtime, when receiving a message (corresponding to some LOB operation), first connect to the LOB to obtain metadata for the operation. This is required in order to understand how to parse the request message (for example - how many parameters does the function have? What are their names? etc). Once metadata is obtained from the LOB, this metadata is cached within the ASDK. There are a number of settings (exposed by the ASDK to the adapter) which determine for how long is the metadata cached, where is it cached, etc. The adapter has control over whether the ASDK should cache the metadata at an AppDomain level (i.e., "static"), or per channel factory instance. The SAP Adapter instructs the ASDK to cache the metadata per channel factory instance. (Why? Another blog post).

At this point, here's what we know - in the SAP Adapter, both, the connection pools, as well as the metadata caches, are maintained within each channel factory object. If you have two channel factories, then you will have a separate set of connection pools and metadata objects. The connection pools and metadata objects are freed up only when the channel factory object is closed.

Now, when you use the Metadata Search Browse tool (using "Add Adapter Service Reference") in your application, we generate an interface (which is the ServiceContract), as well as a client proxy. For example, if you generated the client proxy for RFC_CUSTOMER_GET, you would see a proxy class generated named RfcClient, as well as an interface named Rfc. To use the generated proxy, you would have code similar to:

RfcClient

client = new RfcClient(binding, endpointAddress);
client.Open();
client.RFC_CUSTOMER_GET();
//do work here
client.Close();

Now, here's something which you might not know - every time you create an instance of RfcClient, this causes a new channel factory to be created, and then, from that channel factory, a channel is created. When you close the RfcClient object, the channel is closed, as well as the channel factory. This leads to the connection pool being closed (i.e., all connections in the pool are closed), and the metadata objects are freed up. When you create a new instance of RfcClient, another channel factory is created, which comes with its own pool and metadata cache. Therefore, effectively, connections are not being pooled, (and neither is the metadata cache acting as a cache), since they are freed up the moment the RfcClient object is closed.

So, how do you get around this? One way would be to cache the RfcClient object, and then use that from multiple threads (for example). However, this really doesn't help. The reason being, that the RfcClient object not only represents one channel factory, but also just one channel (which ultimately maps to one LOB connection). Therefore, if you have, say, 50 threads accessing the same RfcClient object, they will all ultimately end up using the same LOB connection, which will definitely not be good for performance.

What you really want to do, is open one channel factory, cache that, and then, open and close channels at will from that channel factory. When you open a channel, a connection will be obtained from the connection pool; and when you close the channel, the connection will be released back to the pool. Plus, the metadata cache will be shared across the channels, since they are part of the same channel factory.

So, how do you this? You can't use the RfcClient proxy. Instead, you need to use the ChannelFactory<> class, and use the generated ServiceContract (which will be named Rfc if you added only RFCs in the "Add Adapter Service Reference" wizard). (You can always figure out the name of the interface, by looking up the definition of the generated proxy, and seeing which interface it implements). Thus, you need to do something like:

//do this only once

ChannelFactory<Rfc> factory = new ChannelFactory<Rfc>(binding);
factory.Open();
//cache the "factory" object somewhere.

//When you need to make a call to SAP:

Rfc channel = factory.CreateChannel(endpointAddress);
channel.RFC_CUSTOMER_GET();
((IChannel)channel).Close();
//that's all.

//When you ultimately want to free up
//all connections in the pool, and free
//up all the stored metadata,

factory.Close();