Buddhike's Weblog

Some insights on calling sync proxy methods from multiple threads

Update: Scott Seely explained that this is by design.  So I'm changing the title in my post. Also if you want to do this, use async methods as he pointed out.

Last night my friend Michele pointed me an interesting thing on the WCF client side runtime. Basically, she had a service method like as follows.

public void SendMessage(string message)
{
    Console.WriteLine("SendMessage: {0}", message);
    MessageBox.Show(String.Format("Received message: '{0}'", message));
}

(We know, we know, one should not display message boxes from a service method but here the idea was basically blocking the thread that executes the service method interactively.)

Further more the service was configured as PerCall/Multiple and was running on netTcp.

Then we tried to call this service with an instance of svcutil generated proxy in several threads simultaneously (see below).

private void button1_Click(object sender, EventArgs e)
{
    MethodInvoker m = new MethodInvoker(CallService);
    m.BeginInvoke(null, null);
}

private void CallService()
{
    proxy.SendMessage(string.Format("Message {0}", ++counter));
}

When we ran client and the service, we expected to see multiple messages boxes appearing in the service as we click on the button1 in the client. In theory this should work because, although we use a single tcp session, our service is configured for PerCall and Multiple. So the message pump in the service side ChannelHandler can use a new thread to pump the next message from the same tcp session and dispatch it to a new service instance object while the previous message is being processed.

However, things did not workout the way we want. When we clicked the button, first message box appeared in the service. But subsequent clicks did not do so until the first message box was closed (i.e. thread was released). However, when all pending messages are displayed, everything started to work as expected for the subsequent requests.

Let the fun begin! :)

Out of curiosity I got a snapshot of all threads while the client is blocked after the few very first calls. Call stack of one blocking service call revealed quite a lot about what's going on. The stack was like this (irrelevant frames and parameters are removed for simplicity sake).

[In a sleep, wait, or join]        
mscorlib.dll!System.Threading.WaitHandle.WaitOne() 
mscorlib.dll!System.Threading.WaitHandle.WaitOne()
System.ServiceModel.dll!System.ServiceModel.TimeoutHelper.WaitOne()        
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.CallOnceManager.SyncWaiter.Wait()
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.CallOnceManager.CallOnce()     
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.EnsureOpened()
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.Call()
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannel.Call()        
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.InvokeService()   
System.ServiceModel.dll!System.ServiceModel.Channels.ServiceChannelProxy.Invoke()
mscorlib.dll!System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke()

Aha! Interesting! On the client side WCF internally uses ServiceChannel.Call to send/receive messages from the service (regardless of whether you use svcutil generated proxy or manual ChannelFactory and IClientChannel). So it seems like the call is not going out of ServiceChannel.Call at all. It's blocking at a call to EnsureOpened method. Then I opened up Reflector hoping to find what's going on in this method (Lutz, I thank you every time I use it :)).

Refelctor demystified some of the interesting stuff WCF do in the client side. When we call service methods, we simply call them with just a single line (we hardly call Open explicitly). Therefore WCF has to make sure that the underlying channel is Open before sending the request. This opening is just one time action. So internally WCF uses a thing called CallOnceManager to make sure that actions like this are performed only once.

The ServiceChannel.EnsureOpened method calls the CallOnceManager which is responsible for calling Channel.Open once to open the underlying channel (look at the following code I extracted from Reflector).

internal void CallOnce(TimeSpan timeout, ServiceChannel.CallOnceManager cascade)
{
    SyncWaiter item = null;
    bool flag = false;
    if (this.queue != null)
    {
        lock (this.ThisLock)
        {
            if (this.queue != null)
            {
                if (this.isFirst)
                {
                    flag = true;
                    this.isFirst = false;
                }
                else
                {
                    item = new SyncWaiter(this);
                    this.queue.Enqueue(item);
                }
            }
        }
    }
    SignalNextIfNonNull(cascade);
    if (flag)
    {
        bool flag2 = true;
        try
        {
            this.callOnce.Call(this.channel, timeout);
            flag2 = false;
        }
        finally
        {
            if (flag2)
            {
                this.SignalNext();
            }
        }
    }
    else if (item != null)
    {
        item.Wait(timeout);
    }
}


CallOnceManager services the simultaneous requests trying to use it for the first time in a FIFO fashion using a queue. This queue is initialized in the ctor. When the CallOnce is invoked it checks whether this queue is available and if "yes", it checks whether this call is the first one. If it's NOT, a waitable object is created and placed in the queue and then CallOnce method blocks on this newly created waitable object.

So out of several simultaneous *first* calls to CallOnce the one that wins open the channel and returns back to the ServiceChannel.Call frame. After doing some more work it finally uses this newly opened channel to send the message. When the reply is returned, it calls CallOnceManager.SingnalIfNotNull method which in turn calls the CallOnceManager.SignalNext method to signal the next waitable object in the queue. When it's signaled the relevant CallOnce call that was waiting on it gets released and the next request is sent to the service.

So in our case first request did not return until we closed the message box. So the subsequent service method calls were hanging at the CallOnceManager.CallOnce method. Because first call has to return and call CallOnceManager.SingnalIfNotNull to get one of the waiting calls released.

IMO, this is too bad. This should essentially check whether the Open has called and if it has, it should just flow without blocking.

On the second part of the question. I was wondering why it was working after end all pending calls. I opened up the SignalNext method and the answer was there.

internal void SignalNext()
{
    if (this.queue != null)
    {
        IWaiter state = null;
        lock (this.ThisLock)
        {
            if (this.queue != null)
            {
                if (this.queue.Count > 0)
                {
                    state = this.queue.Dequeue();
                }
                else
                {
                    this.queue = null;
                }
            }
        }
        if (state != null)
        {
            IOThreadScheduler.ScheduleCallback(signalWaiter, state);
        }
    }
}

When the SignalNext is invoked it dequeues the next waitable object from the queue and signals that. If the queue is empty (i.e. no more items striving to be first), it sets the queue to null. Therefore the next call to CallOnceManager.CallOnce just exists without doing anything because the queue is null.

IMO, this is a little bug we have there ;). Dear team, please correct me if I'm missing anything here or if this is by design please explain us why.

Meanwhile if someone is trying to get over the problem I've also found a nice solution. The CallOnceManager is used for automatic opening of channels. But if we call Open explicitly in our code we can get rid of it (Look at how ServiceChannel.EnsureOpened is called in ServiceChannel.Call method).

if (!this.explicitlyOpened)
{
    this.EnsureDisplayUI();
    this.EnsureOpened(rpc.TimeoutHelper.RemainingTime());
}

So we can turn on this explicitlyOpened flag by calling Open *once* in our proxy or the channel before invoking the method.

After all I started to wonder why I did not spend little bit of more time to reflector the client side runtime. There is a lot of fun out there as well!!! :)

Comments

No Comments