Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I know when it's safe to call Dispose?

I have a search application that takes some time (10 to 15 seconds) to return results for some requests. It's not uncommon to have multiple concurrent requests for the same information. As it stands, I have to process those independently, which makes for quite a bit of unnecessary processing.

I've come up with a design that should allow me to avoid the unnecessary processing, but there's one lingering problem.

Each request has a key that identifies the data being requested. I maintain a dictionary of requests, keyed by the request key. The request object has some state information and a WaitHandle that is used to wait on the results.

When a client calls my Search method, the code checks the dictionary to see if a request already exists for that key. If so, the client just waits on the WaitHandle. If no request exists, I create one, add it to the dictionary, and issue an asynchronous call to get the information. Again, the code waits on the event.

When the asynchronous process has obtained the results, it updates the request object, removes the request from the dictionary, and then signals the event.

This all works great. Except I don't know when to dispose of the request object. That is, since I don't know when the last client is using it, I can't call Dispose on it. I have to wait for the garbage collector to come along and clean up.

Here's the code:

class SearchRequest: IDisposable
{
    public readonly string RequestKey;
    public string Results { get; set; }
    public ManualResetEvent WaitEvent { get; private set; }

    public SearchRequest(string key)
    {
        RequestKey = key;
        WaitEvent = new ManualResetEvent(false);
    }

    public void Dispose()
    {
        WaitEvent.Dispose();
        GC.SuppressFinalize(this);
    }
}

ConcurrentDictionary<string, SearchRequest> Requests = new ConcurrentDictionary<string, SearchRequest>();

string Search(string key)
{
    SearchRequest req;
    bool addedNew = false;
    req = Requests.GetOrAdd(key, (s) =>
        {
            // Create a new request.
            var r = new SearchRequest(s);
            Console.WriteLine("Added new request with key {0}", key);
            addedNew = true;
            return r;
        });

    if (addedNew)
    {
        // A new request was created.
        // Start a search.
        ThreadPool.QueueUserWorkItem((obj) =>
            {
                // Get the results
                req.Results = DoSearch(req.RequestKey);  // DoSearch takes several seconds

                // Remove the request from the pending list
                SearchRequest trash;
                Requests.TryRemove(req.RequestKey, out trash);

                // And signal that the request is finished
                req.WaitEvent.Set();
            });
    }

    Console.WriteLine("Waiting for results from request with key {0}", key);
    req.WaitEvent.WaitOne();
    return req.Results;
}

Basically, I don't know when the last client will be released. No matter how I slice it here, I have a race condition. Consider:

  1. Thread A Creates a new request, starts Thread 2, and waits on the wait handle.
  2. Thread B Begins processing the request.
  3. Thread C detects that there's a pending request, and then gets swapped out.
  4. Thread B Completes the request, removes the item from the dictionary, and sets the event.
  5. Thread A's wait is satisfied, and it returns the result.
  6. Thread C wakes up, calls WaitOne, is released, and returns the result.

If I use some kind of reference counting so that the "last" client calls Dispose, then the object would be disposed by Thread A in the above scenario. Thread C would then die when it tried to wait on the disposed WaitHandle.

The only way I can see to fix this is to use a reference counting scheme and protect access to the dictionary with a lock (in which case using ConcurrentDictionary is pointless) so that a lookup is always accompanied by an increment of the reference count. Whereas that would work, it seems like an ugly hack.

Another solution would be to ditch the WaitHandle and use an event-like mechanism with callbacks. But that, too, would require me to protect the lookups with a lock, and I have the added complication of dealing with an event or a naked multicast delegate. That seems like a hack, too.

This probably isn't a problem currently, because this application doesn't yet get enough traffic for those abandoned handles to add up before the next GC pass comes and cleans them up. And maybe it won't ever be a problem? It worries me, though, that I'm leaving them to be cleaned up by the GC when I should be calling Dispose to get rid of them.

Ideas? Is this a potential problem? If so, do you have a clean solution?

like image 440
Jim Mischel Avatar asked Sep 16 '11 00:09

Jim Mischel


1 Answers

Consider using Lazy<T> for SearchRequest.Results maybe? But that would probably entail a bit of redesign. Haven't thought this out completely.

But what would probably be almost a drop-in replacement for your use case is to implement your own Wait() and Set() methods in SearchRequest. Something like:

object _resultLock;

void Wait()
{
  lock(_resultLock)
  {
     while (!_hasResult)
       Monitor.Wait(_resultLock);
  }
}

void Set(string results)
{
  lock(_resultLock)
  {
     Results = results;
     _hasResult = true;
     Monitor.PulseAll(_resultLock);
  }
}

No need to dispose. :)

like image 108
Ilian Avatar answered Sep 30 '22 12:09

Ilian