Search code examples
javaoracle-databasehibernatesequencejava.util.concurrent

Race condition with StampedLock?


I'm trying to implement my own sequence generator for Hibernate. The out of the box one comes with a synchronized method and this causes too much contention in my application (multiple threads inserting data in parallel into an Oracle database).

I was thinking I will give the StampedLock a try, but unfortunately my test cases (150.000 rows with 16 threads) always produce 5-15 id collisions out of the 150.000 executions.

Attached my code, do you have any idea what I'm doing wrong or can you maybe suggest maybe a better approach? Thank you.

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.StampedLock;

import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.IntegralDataTypeHolder;
import org.hibernate.id.SequenceGenerator;

public class HighConcurrencySequenceGenerator extends SequenceGenerator
{
  private StampedLock            lock   = new StampedLock();

  private AtomicLong             sequenceValue;   // the current generator value
  private IntegralDataTypeHolder lastSourceValue; // last value read from db
  private IntegralDataTypeHolder upperLimitValue; // the value to query the db again

  @Override
  public Serializable generate( SessionImplementor session, Object object )
  {
    long stamp = this.lock.readLock();
    try
    {
      while ( needsSequenceUpdate() )
      {
        long ws = this.lock.tryConvertToWriteLock( stamp );
        if ( ws != 0L )
        {
          stamp = ws;
          return fetchAndGetNextSequenceValue( session );
        }
        this.lock.unlockRead( stamp );
        stamp = this.lock.writeLock();
      }
      return getNextSequenceValue();
    }
    finally
    {
      this.lock.unlock( stamp );
    }
  }

  private long fetchAndGetNextSequenceValue( SessionImplementor session )
  {
    this.lastSourceValue = generateHolder( session );
    long lastSourceValue = this.lastSourceValue.makeValue()
                                               .longValue();
    this.sequenceValue = new AtomicLong( lastSourceValue );

    long nextVal = getNextSequenceValue();

    this.upperLimitValue = this.lastSourceValue.copy()
                                               .add( this.incrementSize );
    return nextVal;
  }

  private long getNextSequenceValue()
  {
    long nextVal = this.sequenceValue.getAndIncrement();
    return nextVal;
  }

  private boolean needsSequenceUpdate()
  {
    return ( this.sequenceValue == null ) || !this.upperLimitValue.gt( this.sequenceValue.get() );
  }
}

Solution

  • I replaced IntegralDataTypeHolder upperLimitValue with an AtomicLong, solved the issue.