Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Global class instance counting (with Semaphores)

I am implementing a class library and looking for a way to restrict the number of instances of a given class the library will allocate to a pre-set number. The restriction has to be machine wide - a simple static counter isn't enough as this will only count the instances in the calling process. I want to keep things as simple as possible (no memory mapped files etc) and as secure as possible (no storing counters in temp files or the registry) so decided to try using a global shared semaphore as the "counter"

public class MyClass : IDisposable
{
   // Limit to 10 instances
   Semaphore m_sem = new Semaphore(10, 10, "SharedName");

   public MyClass()
   {
     if(!m_sem.WaitOne(0))
     {
       throw new Exception("No instances free");
     }
   }

   void IDisposable.Dispose()
   {
     m_sem.Release();
   }
}

That seems to work OK. However if Dispose() doesnt get called, the semaphore never gets released - essentially "leaking" the instance. Now IMHO IDisposable is one of the the worst parts of .NET - I see far more code missing using( ... ) {} than with it. Even worse when you use a IDisposable as a data member and watch as "IDisposable cancer" spreads though every class in your app.

So I decide to implement the full IDisposable (anti-)pattern for people who forget the using()'s.

public class MyClass : IDisposable
{
   // Limit to 10 instances
   Semaphore m_sem = new Semaphore(10, 10, "SharedName");

   public MyClass()
   {
     if(!m_sem.WaitOne(0))
     {
       throw new Exception("No instances left");
     }
   }

   ~MyClass()
   {
      Dispose(false);
   }

   void IDisposable.Dispose()
   {
     Dispose(true);
     GC.SuppressFinalize(this);
   }

   void Dispose(bool disposing)
   {
      if(disposing)
      {
          m_sem.Release();
      }
      else
      {
      // To release or not release?
      // m_sem.Release();
      }
   }
}

Things are simple when being called correctly with using, I release the semapore. But when being called as a last resort by the finalize, as far as I understand it I should not access managed resources as the destruction order isn't fixed - m_sem may have been destroyed.

So, how can I release the semaphore in the case where the user forgets using? (RTFM may be a valid answer, but one I hope to avoid). As it stands, the "leaked" instance is counted right up until the final process that is using my assembly terminates (at which point I assume the global semaphore is freed)

Or indeed is there a better way of doing this?

like image 375
James Hobson Avatar asked Aug 30 '26 14:08

James Hobson


1 Answers

Sometimes, RTFM really is the answer.

I wouldn't necessarily recommend this, but one thing you can do is pin the semaphore object. For example:

public class MyClass : IDisposable
{
   // Limit to 10 instances
   Semaphore m_sem = new Semaphore(10, 10, "SharedName");
   private readonly GCHandle semHandle = GCHandle.Alloc(m_sem);

   public MyClass()
   {
     if(!m_sem.WaitOne(0))
     {
       throw new Exception("No instances free");
     }
   }

   void IDisposable.Dispose()
   {
     semHandle.Free();
     m_sem.Release();
   }
}

I wouldn't do this if I had a whole bunch of these objects because pinning objects can have a negative impact on garbage collector efficiency. But as I understand it, a Normal pinned object isn't a problem (or not as much of a problem).

All things considered, I think I'd prefer the RTFM approach.

like image 141
Jim Mischel Avatar answered Sep 01 '26 02:09

Jim Mischel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!