svn commit: r586306 - /ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r586306 - /ofbiz/trunk/framework/entity/src/org/ofbiz/entity/util/SequenceUtil.java

jonesde
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);
         }
     }