Welcome, Guest. Please login or register.

Author Topic: Semaphores...  (Read 3145 times)

Description:

0 Members and 1 Guest are viewing this topic.

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Semaphores...
« on: November 07, 2005, 11:28:28 PM »
Hi all,

It must be time for another multitasking related question :-)

Let's assume I have an inheritable 'Lockable' class that under AmigaOS wraps a SignalSemaphore. It provides methods such as tryLock() and waitLock() that respectively wrap AttemptSemaphore() and ObtainSemaphore() etc.

All well and good so far. However there is one implementation issue on AmigaOS that nags me slightly.

Let's assume we have three threads that want to lock the object, which has been allocated on the free store and ultimately must be deleted. Deleting can only be achieved by obtaining an exclusive lock first, which is hidden away inside the destructor; that is to say, the implelemtation of ~Lockable() does an ObtainSemaphore()/ReleaseSemaphore() internally as part of te clean up.

Lets start by assuming thread 1 currently has ownership and is busy with it.

In the meantime, thread 2 deletes the object, which causes it to wait for ownership (by virtue of the destructor implementation).

In most cases this is fine. Thread 1 will eventually finish with the object and as soon as thread 2 gets it, it is deleted, no harm no foul.

However, lets assume in this case, a third thread attempts to lock the object while thread 1 still has ownership and thread 2 has already got in the queue ahead of it.

Thread 2 comes next and destroys the object. As the destructor finishes up, it ReleaseSemaphore()'s which means that thread 3 is suddenly signalled that it now has possesion.

This was a problem that I wrote a kludge for. The destructor unwinds any pending locks, setting a flag in each calling task that informed it the object was unavailable causing their calles to waitLock() to return a particular error, after which it is totally illegal to access the object (as it no longer exists).

However this is extremely dirty as I am effectively force unwinding all the calls to ObtainSemaphore() (by repeating a ReleaseSemaphore() until no locks are pending) made by other threads in the thread that is destroying the object.

Whilst it prevents the other threads from getting a lock on someting which no longer exists, ensures they are woken up again and ensures all the calls to ObtainSemaphore() are closed, I am sure this is illegal or at the very least extremely bad style.

Is there a better way or indeed a de facto way of dealing with objects that may be deleted whilst other tasks are waiting for them that ensures those threads simply dont wait forever?

-edit-

I should point out that I don't actually write code that would cause these types of conditions to occur, but I want to ensure that if someone ever did the system copes with it as sensibly as possible.
int p; // A
 

Offline Piru

  • \' union select name,pwd--
  • Hero Member
  • *****
  • Join Date: Aug 2002
  • Posts: 6946
    • Show only replies by Piru
    • http://www.iki.fi/sintonen/
Re: Semaphores...
« Reply #1 on: November 07, 2005, 11:43:03 PM »
The clean way is to make it so that destructor doesn't need to do any locking (that is, it knows no-one can be holding the semaphore). It's the only way if you wish to use the OS signal semaphores.
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Semaphores...
« Reply #2 on: November 07, 2005, 11:48:18 PM »
I think I just had an epiphany...

If the destructor did this:

void Lockable::~Lockable()
{
ObtainSemaphore(&semaphore);
destroyed = true; // all subsequent concurrent lock attempts will fail immediately returning an error to their callers
ReleaseSemaphore(&semaphore); // any pending locks will fail upon getting the lock after this and will release it again before returning an error to their callers
ObtainSemaphore(&semaphore); // wait for final ownership
ReleaseSemaphore(&semaphore);
}

The waitLock() method might be:

bool Lockable::waitLock()
{
// already concurrently undergoing deletion?
if (destroyed)
return false;
ObtainSemaphore(&semaphore);
if (destroyed) {
// were we next in the queue before the destructor flagged it?
ReleaseSemaphore(&semaphore);
return false;
}
// ok we have it
return true;
}

Does this make sense or am I going a bit mad?
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Semaphores...
« Reply #3 on: November 07, 2005, 11:51:46 PM »
Quote

Piru wrote:
The clean way is to make it so that destructor doesn't need to do any locking (that is, it knows no-one can be holding the semaphore). It's the only way if you wish to use the OS signal semaphores.


Yes, but how do you guarentee nobody attemps a lock concurrently with your attempt to delete the object? You can't exactly abort a call to a destructor and return an error because someone was holding or waiting for a lock. Even if yu could, you might not even know the real object being deleted was Lockable, for example.

As I said I don't tend to write that type of code, but I want to be robust.
int p; // A
 

Offline Piru

  • \' union select name,pwd--
  • Hero Member
  • *****
  • Join Date: Aug 2002
  • Posts: 6946
    • Show only replies by Piru
    • http://www.iki.fi/sintonen/
Re: Semaphores...
« Reply #4 on: November 07, 2005, 11:54:15 PM »
@Karlos
Quote
Yes, but how do you guarentee nobody attemps a lock concurrently with your attempt to delete the object?

By making sure there are no objects available that might do that. For example with normal shared library that would be lib_OpenCnt == 0.
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Semaphores...
« Reply #5 on: November 07, 2005, 11:56:46 PM »
@Piru

Unfortunately this is just an implementation part of an OS independent system. A user should be able to delete an object safely (normal C++ rules apply of course) and without knowledge of what else might want it. Similarly the other tasks need to gracefully deal with a failed call to waitLock() or tryLock() as a consequence. Gracefully implies they do get woken out of their wait state (if they already got as far as asking for an exclusive lock that is) and no longer attempt to access the object in any way if the call returned false.
int p; // A
 

Offline CrazyProg

  • Newbie
  • *
  • Join Date: Oct 2004
  • Posts: 14
    • Show only replies by CrazyProg
Re: Semaphores...
« Reply #6 on: November 08, 2005, 12:00:50 AM »
Could you work it with two semaphores.

Semaphore (a) is always gotten with AttemptSemaphoreShared() except by the destructor method, witch does an ObtainSemaphore().

Semaphore (b) is the used for locking purposes.

All the methods lock (a) the lock (b) then free the lock on (a)

For most methods an AttemptSemaphoreShared() on (a) will succeed, if it doesn't then someone is running the destructor().

 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Semaphores...
« Reply #7 on: November 08, 2005, 12:04:05 AM »
Well first of all, the waitLock() method guarentees you an exclusive lock, so it has to do ObtainSemaphore(). Only tryLock() does AttemptSemaphore().

There are also waitReadOnlyLock() and tryReadOnlyLock() which are implemented using Obtain/AttemptSemaphoreShared().

Finally, SignalSemaphores are quite large structures, one is really quite enough to allocate ;-)
int p; // A
 

Offline CrazyProg

  • Newbie
  • *
  • Join Date: Oct 2004
  • Posts: 14
    • Show only replies by CrazyProg
Re: Semaphores...
« Reply #8 on: November 08, 2005, 12:28:53 AM »
Your main problem is controlling what is accessing your methods when you want to run the destructor, and of course avoiding race conditions.  Using a private semaphore within your methods to do this will work.  This is done with libraries/devices all the time.  :-)

Quote

Finally, SignalSemaphores are quite large structures, one is really quite enough to allocate ;-)


As for a SignalSemaphore being large it is only 46 bytes.  

 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Semaphores...
« Reply #9 on: November 08, 2005, 12:31:46 AM »
Ah, I think I misread your first post ;-)

Made a lot more sense second time round :-D

Regarding the size issue though, you have to consider that something inheriting Lockable might itself only be a fraction of the size of the SignalSemaphore ;-)

Viewed in those terms it can be quite significant.
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Semaphores...
« Reply #10 on: November 08, 2005, 01:06:58 AM »
Thanks to you both for the rapid answers. If I am lucky I'll even get to do it before I retire (obligorary gripe about work)
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show only replies by Karlos
Re: Semaphores...
« Reply #11 on: November 08, 2005, 03:27:47 PM »
Just in case anybody was interested...

An early experiment with the code in my second post seems to work.

I think it is conceptually rather like the double semaphore idea, all the user lock calls internally read the 'destroyed' flag but only the destructor sets it. Locks locks are only ever successful if the 'destroyed' flag is false both before and immediately after the call to Obtain/AttemptSemaphore(). You could almost regard the 'destroyed' flag as taking the place of the shared semaphore.

I have tried to create race conditions, so far I didn't manage it.

Of course, not much (other than smart pointers) can deal with the eventuality that one thread attempts to access the object some time after it was successfully deleted, but that is 99% the programmers responsibility (just as it is illegal to access anything that was deleted already).
int p; // A