You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by ad...@apache.org on 2014/09/20 19:02:43 UTC
svn commit: r1626480 - in /ofbiz/branches/release13.07: ./
framework/common/config/ framework/entity/src/org/ofbiz/entity/
framework/entity/src/org/ofbiz/entity/test/
framework/entity/src/org/ofbiz/entity/util/
Author: adrianc
Date: Sat Sep 20 17:02:43 2014
New Revision: 1626480
URL: http://svn.apache.org/r1626480
Log:
Backport recent SequenceUtil.java refactoring to the R13 branch.
Modified:
ofbiz/branches/release13.07/ (props changed)
ofbiz/branches/release13.07/framework/common/config/general.properties
ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/GenericDelegator.java
ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java
ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java
Propchange: ofbiz/branches/release13.07/
------------------------------------------------------------------------------
Merged /ofbiz/trunk:r1621335,1621438,1621442,1621599,1622170
Modified: ofbiz/branches/release13.07/framework/common/config/general.properties
URL: http://svn.apache.org/viewvc/ofbiz/branches/release13.07/framework/common/config/general.properties?rev=1626480&r1=1626479&r2=1626480&view=diff
==============================================================================
--- ofbiz/branches/release13.07/framework/common/config/general.properties (original)
+++ ofbiz/branches/release13.07/framework/common/config/general.properties Sat Sep 20 17:02:43 2014
@@ -130,7 +130,3 @@ mail.spam.value=YES
# -- Y if you want to display the multi-tenant textbox in the login page and install specify components which related to each tenant
multitenant=N
-
-# -- Y if you use a cluster. Most of the time this should not be needed. Setting distributed-cache-clear-enabled="true" is enough
-# -- to guarantee no sequenceIds duplicates. See OFBIZ-2353 and look for "DCC" in wiki for details
-clustered=N
Modified: ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/GenericDelegator.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/GenericDelegator.java?rev=1626480&r1=1626479&r2=1626480&view=diff
==============================================================================
--- ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/GenericDelegator.java (original)
+++ ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/GenericDelegator.java Sat Sep 20 17:02:43 2014
@@ -44,6 +44,7 @@ import org.ofbiz.base.util.UtilFormatOut
import org.ofbiz.base.util.UtilGenerics;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilObject;
+import org.ofbiz.base.util.UtilProperties;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.UtilXml;
import org.ofbiz.entity.cache.Cache;
@@ -68,7 +69,6 @@ import org.ofbiz.entity.model.ModelRelat
import org.ofbiz.entity.model.ModelViewEntity;
import org.ofbiz.entity.serialize.SerializeException;
import org.ofbiz.entity.serialize.XmlSerializer;
-import org.ofbiz.entity.transaction.GenericTransactionException;
import org.ofbiz.entity.transaction.TransactionUtil;
import org.ofbiz.entity.util.DistributedCacheClear;
import org.ofbiz.entity.util.EntityCrypto;
@@ -2337,18 +2337,13 @@ public class GenericDelegator implements
* @see org.ofbiz.entity.Delegator#getNextSeqIdLong(java.lang.String, long)
*/
public Long getNextSeqIdLong(String seqName, long staggerMax) {
- boolean beganTransaction = false;
try {
- if (alwaysUseTransaction) {
- beganTransaction = TransactionUtil.begin();
- }
-
// FIXME: Replace DCL code with AtomicReference
if (sequencer == null) {
synchronized (this) {
if (sequencer == null) {
ModelEntity seqEntity = this.getModelEntity("SequenceValueItem");
- sequencer = new SequenceUtil(this, this.getEntityHelperInfo("SequenceValueItem"), seqEntity, "seqName", "seqId");
+ sequencer = new SequenceUtil(this.getEntityHelperInfo("SequenceValueItem"), seqEntity, "seqName", "seqId");
}
}
}
@@ -2357,16 +2352,10 @@ public class GenericDelegator implements
ModelEntity seqModelEntity = this.getModelEntity(seqName);
Long newSeqId = sequencer == null ? null : sequencer.getNextSeqId(seqName, staggerMax, seqModelEntity);
- TransactionUtil.commit(beganTransaction);
return newSeqId;
} catch (Exception e) {
String errMsg = "Failure in getNextSeqIdLong operation for seqName [" + seqName + "]: " + e.toString() + ". Rolling back transaction.";
Debug.logError(e, errMsg, module);
- try {
- TransactionUtil.rollback(beganTransaction, errMsg, e);
- } catch (GenericTransactionException e1) {
- Debug.logError(e1, "Exception thrown while rolling back transaction: ", module);
- }
throw new GeneralRuntimeException(errMsg, e);
}
}
Modified: ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java?rev=1626480&r1=1626479&r2=1626480&view=diff
==============================================================================
--- ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java (original)
+++ ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/test/EntityTestSuite.java Sat Sep 20 17:02:43 2014
@@ -27,7 +27,14 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.ofbiz.base.concurrent.ExecutionPool;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.Observable;
import org.ofbiz.base.util.Observer;
@@ -44,8 +51,8 @@ import org.ofbiz.entity.condition.Entity
import org.ofbiz.entity.condition.EntityConditionList;
import org.ofbiz.entity.condition.EntityExpr;
import org.ofbiz.entity.condition.EntityOperator;
-import org.ofbiz.entity.config.model.Datasource;
import org.ofbiz.entity.config.EntityConfigUtil;
+import org.ofbiz.entity.config.model.Datasource;
import org.ofbiz.entity.model.ModelEntity;
import org.ofbiz.entity.model.ModelField;
import org.ofbiz.entity.testtools.EntityTestCase;
@@ -1085,6 +1092,62 @@ public class EntityTestSuite extends Ent
assertNull("Delete TestingType 2", testType);
}
+ public void testSequenceValueItem() {
+ SequenceUtil sequencer = new SequenceUtil(delegator.getGroupHelperInfo(delegator.getEntityGroupName("SequenceValueItem")),
+ delegator.getModelEntity("SequenceValueItem"),
+ "seqName", "seqId");
+ UUID id = UUID.randomUUID();
+ String sequenceName = "BogusSequence" + id.toString();
+ for (int i = 10000; i <= 10015; i++) {
+ Long seqId = sequencer.getNextSeqId(sequenceName, 1, null);
+ assertEquals(seqId.longValue(), i);
+ }
+ sequencer.forceBankRefresh(sequenceName, 1);
+ Long seqId = sequencer.getNextSeqId(sequenceName, 1, null);
+ assertEquals(seqId.longValue(), 10020);
+ }
+
+ public void testSequenceValueItemWithConcurrentThreads() {
+ final SequenceUtil sequencer = new SequenceUtil(delegator.getGroupHelperInfo(delegator.getEntityGroupName("SequenceValueItem")),
+ delegator.getModelEntity("SequenceValueItem"),
+ "seqName", "seqId");
+ UUID id = UUID.randomUUID();
+ final String sequenceName = "BogusSequence" + id.toString();
+ final ConcurrentMap<Long, Long> seqIds = new ConcurrentHashMap<Long, Long>();
+ final AtomicBoolean duplicateFound = new AtomicBoolean(false);
+ final AtomicBoolean nullSeqIdReturned = new AtomicBoolean(false);
+
+ List<Future<Void>> futures = new ArrayList<Future<Void>>();
+ Callable getSeqIdTask = new Callable() {
+ public Callable<Void> call() throws Exception {
+ Long seqId = sequencer.getNextSeqId(sequenceName, 1, null);
+ if (seqId == null) {
+ nullSeqIdReturned.set(true);
+ return null;
+ }
+ Long existingValue = seqIds.putIfAbsent(seqId, seqId);
+ if (existingValue != null) {
+ duplicateFound.set(true);
+ }
+ return null;
+ }
+ };
+ Callable refreshTask = new Callable() {
+ public Callable<Void> call() throws Exception {
+ sequencer.forceBankRefresh(sequenceName, 1);
+ return null;
+ }
+ };
+ double probabilityOfRefresh = 0.1;
+ for (int i = 1; i <= 1000; i++) {
+ Callable randomTask = Math.random() < probabilityOfRefresh ? refreshTask : getSeqIdTask;
+ futures.add(ExecutionPool.GLOBAL_EXECUTOR.submit(randomTask));
+ }
+ ExecutionPool.getAllFutures(futures);
+ assertFalse("Null sequence id returned", nullSeqIdReturned.get());
+ assertFalse("Duplicate sequence id returned", duplicateFound.get());
+ }
+
private final class TestObserver implements Observer {
private Observable observable;
private Object arg;
Modified: ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java
URL: http://svn.apache.org/viewvc/ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java?rev=1626480&r1=1626479&r2=1626480&view=diff
==============================================================================
--- ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java (original)
+++ ofbiz/branches/release13.07/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java Sat Sep 20 17:02:43 2014
@@ -22,14 +22,12 @@ import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.util.Hashtable;
-import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import javax.transaction.Transaction;
import org.ofbiz.base.util.Debug;
-import org.ofbiz.base.util.UtilProperties;
-import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.datasource.GenericHelperInfo;
import org.ofbiz.entity.jdbc.ConnectionFactory;
@@ -40,20 +38,18 @@ import org.ofbiz.entity.transaction.Tran
/**
* Sequence Utility to get unique sequences from named sequence banks
- * Uses a collision detection approach to safely get unique sequenced ids in banks from the database
*/
public class SequenceUtil {
public static final String module = SequenceUtil.class.getName();
- private final Map<String, SequenceBank> sequences = new Hashtable<String, SequenceBank>();
+ private final ConcurrentMap<String, SequenceBank> sequences = new ConcurrentHashMap<String, SequenceBank>();
private final GenericHelperInfo helperInfo;
private final String tableName;
private final String nameColName;
private final String idColName;
- private final boolean clustered;
- public SequenceUtil(Delegator delegator, GenericHelperInfo helperInfo, ModelEntity seqEntity, String nameFieldName, String idFieldName) {
+ public SequenceUtil(GenericHelperInfo helperInfo, ModelEntity seqEntity, String nameFieldName, String idFieldName) {
this.helperInfo = helperInfo;
if (seqEntity == null) {
throw new IllegalArgumentException("The sequence model entity was null but is required.");
@@ -73,7 +69,6 @@ public class SequenceUtil {
throw new IllegalArgumentException("Could not find the field definition for the sequence id field " + idFieldName);
}
this.idColName = idField.getColName();
- clustered = delegator.useDistributedCacheClear() || "Y".equals(UtilProperties.getPropertyValue("general.properties", "clustered"));
}
public Long getNextSeqId(String seqName, long staggerMax, ModelEntity seqModelEntity) {
@@ -95,18 +90,14 @@ public class SequenceUtil {
SequenceBank bank = sequences.get(seqName);
if (bank == null) {
- synchronized(this) {
- bank = sequences.get(seqName);
- if (bank == null) {
- long bankSize = SequenceBank.defaultBankSize;
- if (seqModelEntity != null && seqModelEntity.getSequenceBankSize() != null) {
- bankSize = seqModelEntity.getSequenceBankSize().longValue();
- if (bankSize > SequenceBank.maxBankSize) bankSize = SequenceBank.maxBankSize;
- }
- bank = new SequenceBank(seqName, bankSize);
- sequences.put(seqName, bank);
- }
+ long bankSize = SequenceBank.defaultBankSize;
+ if (seqModelEntity != null && seqModelEntity.getSequenceBankSize() != null) {
+ bankSize = seqModelEntity.getSequenceBankSize().longValue();
+ if (bankSize > SequenceBank.maxBankSize) bankSize = SequenceBank.maxBankSize;
}
+ bank = new SequenceBank(seqName, bankSize);
+ SequenceBank bankFromCache = sequences.putIfAbsent(seqName, bank);
+ bank = bankFromCache != null ? bankFromCache : bank;
}
return bank;
@@ -116,57 +107,67 @@ public class SequenceUtil {
public static final long defaultBankSize = 10;
public static final long maxBankSize = 5000;
public static final long startSeqId = 10000;
- public static final long minWaitMillis = 5;
- public static final long maxWaitMillis = 50;
- public static final int maxTries = 5;
- private long curSeqId;
- private long maxSeqId;
private final String seqName;
private final long bankSize;
+ private final String updateForLockStatement;
+ private final String selectSequenceStatement;
+
+ private long curSeqId;
+ private long maxSeqId;
private SequenceBank(String seqName, long bankSize) {
this.seqName = seqName;
curSeqId = 0;
maxSeqId = 0;
this.bankSize = bankSize;
- fillBank(1);
+ updateForLockStatement = "UPDATE " + SequenceUtil.this.tableName + " SET " + SequenceUtil.this.idColName + "=" + SequenceUtil.this.idColName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";
+ selectSequenceStatement = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";
}
- private synchronized Long getNextSeqId(long staggerMax) {
+ private Long getNextSeqId(long staggerMax) {
long stagger = 1;
if (staggerMax > 1) {
- stagger = Math.round(Math.random() * staggerMax);
+ stagger = (long)Math.ceil(Math.random() * staggerMax);
if (stagger == 0) stagger = 1;
}
-
- if ((curSeqId + stagger) <= maxSeqId) {
- Long retSeqId = Long.valueOf(curSeqId);
- curSeqId += stagger;
- return retSeqId;
- } else {
- fillBank(stagger);
+ synchronized (this) {
if ((curSeqId + stagger) <= maxSeqId) {
- Long retSeqId = Long.valueOf(curSeqId);
+ long retSeqId = curSeqId;
curSeqId += stagger;
return retSeqId;
} else {
- Debug.logError("[SequenceUtil.SequenceBank.getNextSeqId] Fill bank failed, returning null", module);
- return null;
+ fillBank(stagger);
+ if ((curSeqId + stagger) <= maxSeqId) {
+ long retSeqId = curSeqId;
+ curSeqId += stagger;
+ return retSeqId;
+ } else {
+ Debug.logError("Fill bank failed, returning null", module);
+ return null;
+ }
}
}
}
- private void refresh(long staggerMax) {
+ private synchronized void refresh(long staggerMax) {
this.curSeqId = this.maxSeqId;
this.fillBank(staggerMax);
}
- private synchronized void fillBank(long stagger) {
- //Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Starting fillBank Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);
-
+ /*
+ The algorithm to get the new sequence id in a thread safe way is the following:
+ 1 - run an update with no changes to get a lock on the record
+ 1bis - if no record is found, try to create and update it to get the lock
+ 2 - select the record (now locked) to get the curSeqId
+ 3 - increment the sequence
+ The three steps are executed in one dedicated database transaction.
+ */
+ private void fillBank(long stagger) {
// no need to get a new bank, SeqIds available
- if ((curSeqId + stagger) <= maxSeqId) return;
+ if ((curSeqId + stagger) <= maxSeqId) {
+ return;
+ }
long bankSize = this.bankSize;
if (stagger > 1) {
@@ -174,180 +175,122 @@ public class SequenceUtil {
bankSize = stagger * defaultBankSize;
}
- if (bankSize > maxBankSize) bankSize = maxBankSize;
-
- long val1 = 0;
- long val2 = 0;
+ if (bankSize > maxBankSize) {
+ bankSize = maxBankSize;
+ }
- // NOTE: the fancy ethernet type stuff is for the case where transactions not available, or something funny happens with really sensitive timing (between first select and update, for example)
- int numTries = 0;
+ Transaction suspendedTransaction = null;
+ try {
+ suspendedTransaction = TransactionUtil.suspend();
+
+ boolean beganTransaction = false;
+ try {
+ beganTransaction = TransactionUtil.begin();
+
+ Connection connection = null;
+ Statement stmt = null;
+ ResultSet rs = null;
- while (val1 + bankSize != val2) {
- if (Debug.verboseOn()) Debug.logVerbose("[SequenceUtil.SequenceBank.fillBank] Trying to get a bank of sequenced ids for " +
- this.seqName + "; start of loop val1=" + val1 + ", val2=" + val2 + ", bankSize=" + bankSize, module);
-
- // not sure if this synchronized block is totally necessary, the method is synchronized but it does do a wait/sleep
- // outside of this block, and this is the really sensitive block, so making sure it is isolated; there is some overhead
- // to this, but very bad things can happen if we try to do too many of these at once for a single sequencer
- synchronized (this) {
- Transaction suspendedTransaction = null;
try {
- //if we can suspend the transaction, we'll try to do this in a local manual transaction
- suspendedTransaction = TransactionUtil.suspend();
-
- boolean beganTransaction = false;
- try {
- beganTransaction = TransactionUtil.begin();
-
- Connection connection = null;
- Statement stmt = null;
- ResultSet rs = null;
-
- try {
- connection = ConnectionFactory.getConnection(SequenceUtil.this.helperInfo);
- } catch (SQLException sqle) {
- Debug.logWarning("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was:" + sqle.toString(), module);
- throw sqle;
- } catch (GenericEntityException e) {
- Debug.logWarning("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database... Error was: " + e.toString(), module);
- throw e;
- }
-
- if (connection == null) {
- throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database, connection was null...");
- }
-
- String sql = null;
+ connection = ConnectionFactory.getConnection(SequenceUtil.this.helperInfo);
+ } catch (SQLException sqle) {
+ Debug.logWarning("Unable to esablish a connection with the database. Error was:" + sqle.toString(), module);
+ throw sqle;
+ } catch (GenericEntityException e) {
+ Debug.logWarning("Unable to esablish a connection with the database. Error was: " + e.toString(), module);
+ throw e;
+ }
+ if (connection == null) {
+ throw new GenericEntityException("Unable to esablish a connection with the database, connection was null...");
+ }
+ try {
+ stmt = connection.createStatement();
+ String sql = null;
+ // 1 - run an update with no changes to get a lock on the record
+ if (stmt.executeUpdate(updateForLockStatement) <= 0) {
+ Debug.logWarning("Lock failed; no sequence row was found, will try to add a new one for sequence: " + seqName, module);
+ sql = "INSERT INTO " + SequenceUtil.this.tableName + " (" + SequenceUtil.this.nameColName + ", " + SequenceUtil.this.idColName + ") VALUES ('" + this.seqName + "', " + startSeqId + ")";
try {
- // we shouldn't need this, and some TX managers complain about it, so not including it: connection.setAutoCommit(false);
-
- stmt = connection.createStatement();
- if (clustered) {
- sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'" + " FOR UPDATE";
- } else {
- sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";
- }
- rs = stmt.executeQuery(sql);
- boolean gotVal1 = false;
- if (rs.next()) {
- val1 = rs.getLong(SequenceUtil.this.idColName);
- gotVal1 = true;
- }
- rs.close();
-
- if (!gotVal1) {
- Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] first select failed: will try to add new row, result set was empty for sequence [" + seqName + "] \nUsed SQL: " + sql + " \n Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);
- sql = "INSERT INTO " + SequenceUtil.this.tableName + " (" + SequenceUtil.this.nameColName + ", " + SequenceUtil.this.idColName + ") VALUES ('" + this.seqName + "', " + startSeqId + ")";
- if (stmt.executeUpdate(sql) <= 0) {
- throw new GenericEntityException("No rows changed when trying insert new sequence row with this SQL: " + sql);
- }
- continue;
- }
-
- sql = "UPDATE " + SequenceUtil.this.tableName + " SET " + SequenceUtil.this.idColName + "=" + SequenceUtil.this.idColName + "+" + bankSize + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";
- if (stmt.executeUpdate(sql) <= 0) {
- throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] update failed, no rows changes for seqName: " + seqName);
- }
- if (clustered) {
- sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'" + " FOR UPDATE";
-
- } else {
- sql = "SELECT " + SequenceUtil.this.idColName + " FROM " + SequenceUtil.this.tableName + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";
- }
- rs = stmt.executeQuery(sql);
- boolean gotVal2 = false;
- if (rs.next()) {
- val2 = rs.getLong(SequenceUtil.this.idColName);
- gotVal2 = true;
- }
-
- rs.close();
-
- if (!gotVal2) {
- throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] second select failed: aborting, result set was empty for sequence: " + seqName);
- }
-
- // got val1 and val2 at this point, if we don't have the right difference between them, force a rollback (with
- //setRollbackOnly and NOT with an exception because we don't want to break from the loop, just err out and
- //continue), then flow out to allow the wait and loop thing to happen
- if (val1 + bankSize != val2) {
- TransactionUtil.setRollbackOnly("Forcing transaction rollback in sequence increment because we didn't get a clean update, ie a conflict was found, so not saving the results", null);
- }
+ stmt.executeUpdate(sql);
} catch (SQLException sqle) {
- Debug.logWarning(sqle, "[SequenceUtil.SequenceBank.fillBank] SQL Exception while executing the following:\n" + sql + "\nError was:" + sqle.getMessage(), module);
- throw sqle;
- } finally {
- try {
- if (stmt != null) stmt.close();
- } catch (SQLException sqle) {
- Debug.logWarning(sqle, "Error closing statement in sequence util", module);
- }
- try {
- if (connection != null) connection.close();
- } catch (SQLException sqle) {
- Debug.logWarning(sqle, "Error closing connection in sequence util", module);
+ // insert failed: this means that another thread inserted the record; then retry to run an update with no changes to get a lock on the record
+ if (stmt.executeUpdate(updateForLockStatement) <= 0) {
+ // This should never happen
+ throw new GenericEntityException("No rows changed when trying insert new sequence: " + seqName);
}
- }
- } catch (Exception e) {
- String errMsg = "General error in getting a sequenced ID";
- Debug.logError(e, errMsg, module);
- try {
- TransactionUtil.rollback(beganTransaction, errMsg, e);
- } catch (GenericTransactionException gte2) {
- Debug.logError(gte2, "Unable to rollback transaction", module);
- }
- // error, break out of the loop to not try to continue forever
- break;
- } finally {
- try {
- TransactionUtil.commit(beganTransaction);
- } catch (GenericTransactionException gte) {
- Debug.logError(gte, "Unable to commit sequence increment transaction, continuing anyway though", module);
}
}
- } catch (GenericTransactionException e) {
- Debug.logError(e, "System Error suspending transaction in sequence util", module);
+ // 2 - select the record (now locked) to get the curSeqId
+ rs = stmt.executeQuery(selectSequenceStatement);
+ boolean sequenceFound = rs.next();
+ if (sequenceFound) {
+ curSeqId = rs.getLong(SequenceUtil.this.idColName);
+ }
+ rs.close();
+ if (!sequenceFound) {
+ throw new GenericEntityException("Failed to find the sequence record for sequence: " + seqName);
+ }
+ // 3 - increment the sequence
+ sql = "UPDATE " + SequenceUtil.this.tableName + " SET " + SequenceUtil.this.idColName + "=" + SequenceUtil.this.idColName + "+" + bankSize + " WHERE " + SequenceUtil.this.nameColName + "='" + this.seqName + "'";
+ if (stmt.executeUpdate(sql) <= 0) {
+ throw new GenericEntityException("Update failed, no rows changes for seqName: " + seqName);
+ }
+
+ TransactionUtil.commit(beganTransaction);
+
+ } catch (SQLException sqle) {
+ Debug.logWarning(sqle, "SQL Exception:" + sqle.getMessage(), module);
+ throw sqle;
} finally {
- if (suspendedTransaction != null) {
- try {
- TransactionUtil.resume(suspendedTransaction);
- } catch (GenericTransactionException e) {
- Debug.logError(e, "Error resuming suspended transaction in sequence util", module);
- }
+ try {
+ if (stmt != null) stmt.close();
+ } catch (SQLException sqle) {
+ Debug.logWarning(sqle, "Error closing statement in sequence util", module);
+ }
+ try {
+ if (connection != null) connection.close();
+ } catch (SQLException sqle) {
+ Debug.logWarning(sqle, "Error closing connection in sequence util", module);
}
}
- }
-
- if (val1 + bankSize != val2) {
- if (numTries >= maxTries) {
- String errMsg = "[SequenceUtil.SequenceBank.fillBank] maxTries (" + maxTries + ") reached for seqName [" + this.seqName + "], giving up.";
- Debug.logError(errMsg, module);
- return;
+ } catch (Exception e) {
+ // reset the sequence fields and return (note: it would be better to throw an exception)
+ curSeqId = 0;
+ maxSeqId = 0;
+ String errMsg = "General error in getting a sequenced ID";
+ Debug.logError(e, errMsg, module);
+ try {
+ TransactionUtil.rollback(beganTransaction, errMsg, e);
+ } catch (GenericTransactionException gte2) {
+ Debug.logError(gte2, "Unable to rollback transaction", module);
}
-
- // collision happened, wait a bounded random amount of time then continue
- long waitTime = (long) (Math.random() * (maxWaitMillis - minWaitMillis) + minWaitMillis);
-
- Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Collision found for seqName [" + seqName + "], val1=" + val1 + ", val2=" + val2 + ", val1+bankSize=" + (val1 + bankSize) + ", bankSize=" + bankSize + ", waitTime=" + waitTime, module);
-
+ return;
+ }
+ } catch (GenericTransactionException e) {
+ Debug.logError(e, "System Error suspending transaction in sequence util", module);
+ // reset the sequence fields and return (note: it would be better to throw an exception)
+ curSeqId = 0;
+ maxSeqId = 0;
+ return;
+ } finally {
+ if (suspendedTransaction != null) {
try {
- // using the Thread.sleep to more reliably lock this thread: this.wait(waitTime);
- java.lang.Thread.sleep(waitTime);
- } catch (Exception e) {
- Debug.logWarning(e, "Error waiting in sequence util", module);
+ TransactionUtil.resume(suspendedTransaction);
+ } catch (GenericTransactionException e) {
+ Debug.logError(e, "Error resuming suspended transaction in sequence util", module);
+ // reset the sequence fields and return (note: it would be better to throw an exception)
+ curSeqId = 0;
+ maxSeqId = 0;
return;
}
}
-
- numTries++;
}
- curSeqId = val1;
- maxSeqId = val2;
+ maxSeqId = curSeqId + bankSize;
if (Debug.infoOn()) Debug.logInfo("Got bank of sequenced IDs for [" + this.seqName + "]; curSeqId=" + curSeqId + ", maxSeqId=" + maxSeqId + ", bankSize=" + bankSize, module);
- //Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Ending fillBank Thread Name is: " + Thread.currentThread().getName() + ":" + Thread.currentThread().toString(), module);
}
}
}
+