Workflow with one-way contract (only Receive), NamedPipe transport with TransferMode = Buffered – observed behavior

I came across this behavior a few days back while investigating a bug.

 

Here’s what I had:

Workflow:

1. Has a number of activities executing in sequence. None of the activities are of type AsyncCodeActivity, and none of the activities create Bookmarks.

2. The Workflow starts off with a Receive, and has no SendReply.

3. Workflow hosted in a Workflow Service Host.

Endpoint:

1. The binding used was a custom binding, using the NamedPipesTransportBindingElement. The TransferMode property was set to Buffered.

Client:

1. Client creates an IDuplexSessionChannel.

2. Client invokes channel.Open().

3. Client invokes channel.Send(Message).

4. Client invokes channel.Close().

 

Expected Behavior:

Client:

1. Channel.Close() should return immediately (since when channel.Send() has returned, it should imply that the message was dispatched to the service).

 

Observed Behavior:

Client:

1. Channel.Close() gets blocked until the Workflow completes.

 

Interpretation:

Since a session is used, when the client invokes channel.Close(), the session needs to be closed. This involves a call being made to the WCF Service (the workflow in this case), and the Service needs to somehow “respond”. Since the workflow was busy executing synchronous activities, it wasn’t able to respond, and thus Close() got blocked until the Workflow Runtime became “available”.

 

Workarounds (each of these is individually a workaround):

Workflow:

1. Add a Delay activity with Timespan of at least 1 millisecond. This causes a bookmark to get created, and the workflow runtime is free to do something else until the bookmark resumes. (Of course, the assumption here is that the client’s channel.Close() call reaches the workflow service before the bookmark got resumed).

2. Change one of the activities (preferably the one just after the Receive) to be an AsyncCodeActivity (assuming it does not complete synchronously). Again, this frees up the workflow runtime.

Endpoint:

1. Use a Binding which does not require sessions (e.g. one of the Http Bindings).

2. Change the TransferMode setting to Streamed on NamedPipesTransportBindingElement. You can now use an IRequestChannel instead of IDuplexSessionChannel. Since your workflow doesn’t have a SendReply, the response Message returned when you invoke channel.Request(Message) will be null.

Client:

1. Use Channel.BeginClose() instead of Channel.Close(). This way, your main thread doesn’t get blocked.