Author: jonesde
Date: Fri Oct 19 01:13:17 2007 New Revision: 586306 URL: http://svn.apache.org/viewvc?rev=586306&view=rev Log: Some refactoring of the sequence bank refill code: reorganized so that transaction management is inside the loop instead of out, causing each attempt to be in its own transaction to avoid doing mean things like locking and waiting; also added an extra synchronized block around the transaction that does the select/update/select stuff to protect it extra and avoid problems with waiting/sleeping; this is tested minimally and needs more thorough testng to see if it solved the problems seen Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java Modified: ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java?rev=586306&r1=586305&r2=586306&view=diff ============================================================================== --- ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java (original) +++ ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java Fri Oct 19 01:13:17 2007 @@ -178,150 +178,167 @@ long val1 = 0; long val2 = 0; - // NOTE: the fancy ethernet type stuff is for the case where transactions not available - 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(parentUtil.helperName); - } 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; + // 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; + 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 syncrhonized block is totally necessary, the method is syncrhonized 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 { - // we shouldn't need this, and some TX managers complain about it, so not including it: connection.setAutoCommit(false); - - stmt = connection.createStatement(); - int numTries = 0; - - 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); - - sql = "SELECT " + parentUtil.idColName + " FROM " + parentUtil.tableName + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'"; - rs = stmt.executeQuery(sql); - boolean gotVal1 = false; - if (rs.next()) { - val1 = rs.getLong(parentUtil.idColName); - gotVal1 = true; + //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(parentUtil.helperName); + } 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; } - 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 " + parentUtil.tableName + " (" + parentUtil.nameColName + ", " + parentUtil.idColName + ") VALUES ('" + this.seqName + "', " + startSeqId + ")"; + if (connection == null) { + throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank]: Unable to esablish a connection with the database, connection was null..."); + } + + String sql = null; + + try { + // we shouldn't need this, and some TX managers complain about it, so not including it: connection.setAutoCommit(false); + + stmt = connection.createStatement(); + + sql = "SELECT " + parentUtil.idColName + " FROM " + parentUtil.tableName + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'"; + rs = stmt.executeQuery(sql); + boolean gotVal1 = false; + if (rs.next()) { + val1 = rs.getLong(parentUtil.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 " + parentUtil.tableName + " (" + parentUtil.nameColName + ", " + parentUtil.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 " + parentUtil.tableName + " SET " + parentUtil.idColName + "=" + parentUtil.idColName + "+" + bankSize + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'"; if (stmt.executeUpdate(sql) <= 0) { - throw new GenericEntityException("No rows changed when trying insert new sequence row with this SQL: " + sql); + throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] update failed, no rows changes for seqName: " + seqName); } - continue; - } - - sql = "UPDATE " + parentUtil.tableName + " SET " + parentUtil.idColName + "=" + parentUtil.idColName + "+" + bankSize + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'"; - if (stmt.executeUpdate(sql) <= 0) { - throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] update failed, no rows changes for seqName: " + seqName); - } - - sql = "SELECT " + parentUtil.idColName + " FROM " + parentUtil.tableName + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'"; - rs = stmt.executeQuery(sql); - boolean gotVal2 = false; - if (rs.next()) { - val2 = rs.getLong(parentUtil.idColName); - gotVal2 = true; - } - - rs.close(); - - if (!gotVal2) { - throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] second select failed: aborting, result " + "set was empty for sequence: " + seqName); + + sql = "SELECT " + parentUtil.idColName + " FROM " + parentUtil.tableName + " WHERE " + parentUtil.nameColName + "='" + this.seqName + "'"; + rs = stmt.executeQuery(sql); + boolean gotVal2 = false; + if (rs.next()) { + val2 = rs.getLong(parentUtil.idColName); + gotVal2 = true; + } + + rs.close(); - } - - if (val1 + bankSize != val2) { - if (numTries >= maxTries) { - throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] maxTries (" + maxTries + ") reached, giving up."); + if (!gotVal2) { + throw new GenericEntityException("[SequenceUtil.SequenceBank.fillBank] second select failed: aborting, result set was empty for sequence: " + seqName); } - // collision happened, wait a bounded random amount of time then continue - int waitTime = (new Double(Math.random() * (maxWaitMillis - minWaitMillis))).intValue() + minWaitMillis; - - Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Collision found for seqName [" + seqName + "], val1=" + val1 + ", val2=" + val2 + ", val1+bankSize=" + (val1 + bankSize) + ", bankSize=" + bankSize + ", waitTime=" + waitTime, module); - + // 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); + } + } 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 { - // 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); - throw e; + 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); } } - - numTries++; + } 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); + } } - - curSeqId = val1; - maxSeqId = val2; - if (Debug.infoOn()) Debug.logInfo("Got bank of sequenced IDs for [" + this.seqName + "]; curSeqId=" + curSeqId + ", maxSeqId=" + maxSeqId + ", bankSize=" + bankSize, module); - } catch (SQLException sqle) { - Debug.logWarning(sqle, "[SequenceUtil.SequenceBank.fillBank] SQL Exception while executing the following:\n" + sql + "\nError was:" + sqle.getMessage(), module); - throw sqle; + } catch (GenericTransactionException e) { + Debug.logError(e, "System Error suspending transaction in sequence util", module); } 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); + if (suspendedTransaction != null) { + try { + TransactionUtil.resume(suspendedTransaction); + } catch (GenericTransactionException e) { + Debug.logError(e, "Error resuming suspended transaction in sequence util", module); + } } } - } 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); - } - } finally { - try { - TransactionUtil.commit(beganTransaction); - } catch (GenericTransactionException gte) { - Debug.logError(gte, "Unable to commit transaction", module); - } } - } catch (GenericTransactionException e) { - Debug.logError(e, "System Error suspending transaction in sequence util", module); - } finally { - if (suspendedTransaction != null) { + + 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; + } + + // collision happened, wait a bounded random amount of time then continue + int waitTime = (new Double(Math.random() * (maxWaitMillis - minWaitMillis))).intValue() + minWaitMillis; + + Debug.logWarning("[SequenceUtil.SequenceBank.fillBank] Collision found for seqName [" + seqName + "], val1=" + val1 + ", val2=" + val2 + ", val1+bankSize=" + (val1 + bankSize) + ", bankSize=" + bankSize + ", waitTime=" + waitTime, module); + try { - TransactionUtil.resume(suspendedTransaction); - } catch (GenericTransactionException e) { - Debug.logError(e, "Error resuming suspended transaction in sequence util", module); + // 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); + return; } } + + numTries++; } + + curSeqId = val1; + maxSeqId = val2; + 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); } } |
Free forum by Nabble | Edit this page |