Given the simple socket client class below, connect it to a TCP server (I use SocketTest3, freely available online). Then disconnect the server and wait for a bit. You should get a LockRecursionException
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace SocketRwlTest
{
public class SocketRwlTest
{
private Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
private readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
private const int maxLength = 200;
public SocketRwlTest(IPAddress address, ushort port)
{
client.Connect(new IPEndPoint(address, port));
ReceiveOne();
}
private void ReceiveOne()
{
rwl.EnterReadLock();
try
{
var inArray = new byte[maxLength];
client.BeginReceive(inArray, 0, maxLength, 0,
new AsyncCallback(ReceivedCallback),
inArray);
}
finally
{
rwl.ExitReadLock();
}
}
private void ReceivedCallback(IAsyncResult ar)
{
client.EndReceive(ar);
ReceiveOne();
}
}
}
I don't understand why it happens in the simplified example given. I know I should stop calling ReceiveOne
as soon as I receive a zero-length message, but this is more of an exercise. I wondered if a similar bug could maintain a constant stream of callbacks running in the background and stealing resources without obviously bad things happening. I must admit I wasn't expecting this particular exception.
Question 1: Why does this happen? Are BeginXYZ
methods perhaps allowed to execute callbacks instantly, on the same thread? If that's the case, who's to say this couldn't happen during normal runtime?
Question 2: Are there ways to avoid getting this exception while still maintaining the "desired" behaviour in this case? I mean fire a non-stopping stream of callbacks.
I'm using Visual Studio 2010 with .NET 4.
Question 1: Why does this happen? Are BeginXYZ methods perhaps allowed to execute callbacks instantly, on the same thread? If that's the case, who's to say this couldn't happen during normal runtime?
As described by mike z in the comments, the BeginReceive()
method is not required to execute asynchronously. If data is available it will execute synchronously, calling the callback delegate in the same thread. This is by definition a recursive call, and so would not be compatible with the use of a non-recursive lock object (such as the ReaderWriterLockSlim
you're using here).
This certainly can happen "during normal runtime". I'm not sure I understand the second part of your question. Who's to say it can't happen? No one. It can happen.
Question 2: Are there ways to avoid getting this exception while still maintaining the "desired" behaviour in this case? I mean fire a non-stopping stream of callbacks.
I'm afraid I also don't know what you mean by "fire a non-stopping stream of callbacks".
One obvious workaround is to enable recursion on the ReaderWriterLockSlim
object by passing LockRecursionPolicy.SupportsRecursion
to its constructor. Alternatively, you could check the IsReadLockHeld
property before trying to take the lock.
It is not clear from your code example why you have the lock at all, never mind why it's used in that specific way. It's possible the right solution is to not hold the lock at all while you call BeginReceive()
. Use it only while processing the result from EndReceive()
.