ISemaphore tryAcquire methods don't work as advertised

398 views
Skip to first unread message

Devin Greene

unread,
Feb 1, 2013, 12:38:59 PM2/1/13
to haze...@googlegroups.com
The tryAcquire method in ISemaphore states:

If no permit is available then this method will return immediately with the value {@code false}.

In my testing, however, this is plainly not the case. The following unit test clearly shows this problem as after the main thread acquires all of the available permits, subsequent calls to tryAcquire block and the test hangs. Those calls should return false immediately.

I am running an old version of Hazelcast (1.9.4.4). I tried the test with Hazelcast 2.5, and it 'works' (although the method still is blocking for 1000ms each time) , so clearly something was fixed. However, I can't find any bug on Github about this.

This issue has come up at a bad time in my project's lifecycle. It's too late to be switching to a completely different version of Hazelcast that breaks API and requires code changes. Do you know when this change was fixed? Can I get away with a slightly newer version of Hazelcast that doesn't require all the code changes? Will you backport the fix to the 1.9.4 branch?

Or maybe there is another way to get what I want. I'm basically using a ISemaphore with a single permit to act as an exclusion lock. I need to be able to tell easily, however, if the lock is being held by anyone in the cluster without actually taking the lock. The ISemaphore.availablePermits() was perfect for this. ILock doesn't have this ability.

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.hazelcast.config.Config;
import com.hazelcast.config.SemaphoreConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.ISemaphore;


public class TestHazelcast {

    private static final int NUM_PERMITS = 1;
    private static final String SEMAPHORE_NAME = "mySemaphore";
    private HazelcastInstance hcInstance;

    @Before
    public void setup() {
        Config config = Hazelcast.getConfig();
        config.addSemaphoreConfig( new SemaphoreConfig( SEMAPHORE_NAME, NUM_PERMITS ) );
        hcInstance = Hazelcast.newHazelcastInstance( config );
    }

    @After
    public void tearDown() {
        Hazelcast.shutdownAll();
    }

    @Test
    public void testHazelcastISemaphore() throws InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        final CountDownLatch latchOne = new CountDownLatch( 1 );
        final CountDownLatch latchTwo = new CountDownLatch( 1 );
        final ISemaphore semaphore = hcInstance.getSemaphore( SEMAPHORE_NAME );
        assertTrue( semaphore.tryAcquire( NUM_PERMITS) );
        executor.submit( new Runnable() {
            @Override
            public void run() {
                try {
                    latchOne.await();
                    semaphore.release(NUM_PERMITS);
                    latchTwo.countDown();
                }
                catch ( InterruptedException e ) {
                    e.printStackTrace();
                }
            }
        } );

        for( int i = 0; i < 10; ++i ) {
            // We shouldn't be able to acquire any permits now
            // This is where the test hangs with 1.9.4.4
            // With 2.5, it still blocks for 1000ms, but does return as expected
            System.out.println( "tryAcquiring " + i + ", " + System.currentTimeMillis());
            assertFalse( semaphore.tryAcquire() );
        }
        latchOne.countDown();
        latchTwo.await();
        // Now we should be able to acquire a permit again
        assertTrue( semaphore.tryAcquire() );
        // cleanup
        semaphore.release();
    }
}

Tim Peierls

unread,
Feb 1, 2013, 1:44:47 PM2/1/13
to haze...@googlegroups.com
If you need cluster-wide exclusion with the ability to test from any node in the cluster (which is not what your code sample suggests, but *is* what your prose indicates), then ICountDownLatch might do the trick.

Try to acquire: 

if (latch.setCount(1)) {
    // lock acquired
} else {
    // lock not acquired
}

Test whether in use:

boolean inUse = latch.hasCount();

Release:

latch.countDown();

Wait for availability:

latch.await();
// latch.await(time, unit);

--tim


--
You received this message because you are subscribed to the Google Groups "Hazelcast" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hazelcast+...@googlegroups.com.
To post to this group, send email to haze...@googlegroups.com.
Visit this group at http://groups.google.com/group/hazelcast?hl=en-US.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Devin Greene

unread,
Feb 4, 2013, 12:45:45 PM2/4/13
to haze...@googlegroups.com, t...@peierls.net
Thanks Tim, I had forgotten that the ICountDownLatch is reusable, unlike CountDownLatch.

What I ended up doing, however, was doing an additional check before acquiring the lock.

Instead of this:

if( semaphore.tryAcquire() ) {
    // do stuff
}

I now go

if( semaphore.availablePermits() != 0 && semaphore.tryAcquire() ) {
    // do stuff
}

which avoids the call to tryAcquire if there are no permits available. This is lame, but seems to work for my needs.
I'd still like to know when the ISemaphore behavior got fixed, however.

Tim Peierls

unread,
Feb 4, 2013, 1:21:51 PM2/4/13
to haze...@googlegroups.com
On Mon, Feb 4, 2013 at 12:45 PM, Devin Greene <ddg...@gmail.com> wrote:
if( semaphore.availablePermits() != 0 && semaphore.tryAcquire() ) {
    // do stuff
}

which avoids the call to tryAcquire if there are no permits available. This is lame, but seems to work for my needs.

I don't think it's lame, but I do think it has a race: What happens if, after a call to availablePermits that returns > 0 but before a call to tryAcquire, some other thread/node acquires all the remaining permits? Then you're back at square one.

I know it seems unlikely, but even if it happens once in a blue moon, that's still too much.

--tim

Devin Greene

unread,
Feb 4, 2013, 1:59:18 PM2/4/13
to haze...@googlegroups.com
You are correct that there is a race condition. In my case it is okay if it happens rarely, as the application will carry on, it just might repeat some work more frequently than it should. The major issue I had was when it was happening every time.


--
Reply all
Reply to author
Forward
0 new messages