[ 
https://issues.apache.org/jira/browse/DERBY-633?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Mike Matrigali updated DERBY-633:
---------------------------------

    Urgency: Normal

Triaged July 2, 2009:  assigned normal urgency.

A tool was run that marked possible problems, but no repro or report of an 
actual problem.

> synchronization issues in client code
> -------------------------------------
>
>                 Key: DERBY-633
>                 URL: https://issues.apache.org/jira/browse/DERBY-633
>             Project: Derby
>          Issue Type: Bug
>          Components: Network Client
>    Affects Versions: 10.1.1.0
>            Reporter: Mayur Naik
>
> I ran a static race detection tool to check the implementation of the 
> following java.sql.* interfaces in Derby and found 319 possible bugs (static 
> instances of missing synchronization):
> Blob
> Clob
> DatabaseMetaData
> Connection
> Statement
> PreparedStatement
> CallableStatement
> ResultSet
> I ran the tool in 2 passes.  The results of the 2 passes are here:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/
> PASS1 reports "shallow" races, which I fixed and re-ran the tool, and PASS2 
> reports any remaining deep races.  I fixed those as well and re-ran the tool, 
> and it did not report any more races.  The 2 passes are because a single 
> instance of bad synchronization can cause multiple races (and, conversely, a 
> single race can indicate multiple instances of bad synchronization), so to 
> prevent overwhelming the user with race reports, the first pass checks only 
> for races on fields of top-level objects (ex. NetConnection) but the second 
> pass does a full-blown check.
> ====================================================================
> Analysis of results of PASS 1:
> ====================================================================
> ===============================
> java.sql.Blob
> 0 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/blob/index.html
> ===============================
> java.sql.Clob
> 0 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/clob/index.html
> ===============================
> java.sql.Connection
> 147 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/connection/index.html
> These are due to lack of synchronization on 'this' in 12 methods in class 
> Connection:
> boolean getAutoCommit()
> boolean isClosed()
> int getTransactionIsolation()
> java.sql.SQLWarning getWarnings()
> java.sql.DatabaseMetaData getMetaData()
> boolean isReadOnly()
> String getCatalog()
> java.util.Map getTypeMap()
> int getHoldability()
> java.sql.PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
> java.sql.PreparedStatement prepareStatement(String sql, int columnIndexes[])
> java.sql.PreparedStatement prepareStatement(String sql, String columnNames[])
> In most of the above cases, it is the classic situation of possibly incorrect 
> synchronization in which the programmer leaves get* methods unsychronized.  
> It may or may not be correct depending upon what those get* methods are 
> doing.  I leave it to the programmer to decide in this case.  Also, it is not 
> sufficient to reason at the Java level: given the subtleties of the Java 
> memory model, one needs to reason at the bytecode level (see the next item 
> for more on this).
> ===============================
> java.sql.DatabaseMetaData
> 6 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/database_metadata/index.html
> All are on field metaDataInfoIsCached_ of class DatabaseMetaData due to lack 
> of synchronization on connection_ in 107 public methods in this class that 
> call a getMetaData* method.  One of these 107 methods is:
>     public boolean supportsBatchUpdates() throws SqlException {
>         checkForClosedConnection();
>         return getMetaDataInfoBoolean(supportsBatchUpdates__);
>     }
> The programmer seems to be aware of these races b/w the protected write 
> access to field metaDataInfoIsCached_ in the below method
>     // We synchronize at this level so that we don't have to synchronize all
>     // the meta data info methods.  If we just return hardwired answers we
>     // don't need to synchronize at the higher level.
>     private void metaDataInfoCall() {
>         synchronized (connection_) {
>             ... // update metaDataInfoCache_
>             metaDataInfoIsCached_ = true;
>         }
>     }
> and the unprotected read access to that field in each of the getMetaData* 
> methods, for instance:
>     private String getMetaDataInfoString(int infoCallIndex) {
>         if (metaDataInfoIsCached_) {
>             return (String) metaDataInfoCache_[infoCallIndex];
>         } 
>         ...
>     }
> The races are benign if one reasons at the Java level, but they might be 
> worth looking at from the perspective of the bytecode level.  In particular, 
> since a JVM is free to reorder instructions within a synchronized block, it 
> is legal (though highly unlikely) for it to move the write to 
> metaDataInfoIsCached_ in the metaDataInfoCall method to before the "..." code 
> that updates the elements of array metaDataInfoCache_, in which case there is 
> clearly a bug.  I myself am not very familiar with the Java memory model, but 
> see http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html for 
> some unexpected things that can happen due to reordering of bytecode which is 
> legal within (but not across) a synchronization block.
> ===============================
> java.sql.Statement
> 20 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/statement/index.html
> These are due to lack of synchronization on connection_ in 14 methods in 
> class Statement:
> int getMaxFieldSize()
> int getMaxRows()
> int getQueryTimeout()
> void cancel()
> java.sql.SQLWarning getWarnings() 
> int getFetchDirection()
> int getFetchSize() 
> int getResultSetConcurrency()
> int getResultSetType()
> java.sql.Connection getConnection() 
> java.sql.ResultSet getGeneratedKeys() 
> int executeUpdate(String sql, int columnIndexes[]) 
> boolean execute(String sql, int columnIndexes[])
> int getResultSetHoldability() 
> Most of these are due to unsynchronized get* methods (similar to Connection 
> above).
> ===============================
> java.sql.PreparedStatement
> 0 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/prepared_statement/index.html
> ===============================
> java.sql.CallableStatement
> 26 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/callable_statement/index.html
> All are on field wasNull_ due to lack of synchronization on connection_ in 
> the wasNull method in class CallableStatement.
> ===============================
> java.sql.ResultSet
> 764 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS1/result_set/index.html
> These are due to lack of synchronization on connection_ in 87 methods in 
> class ResultSet:
> boolean wasNull()
> boolean getBoolean(int column)
> byte getByte(int column)
> short getShort(int column)
> int getInt(int column)
> long getLong(int column)
> float getFloat(int column)
> double getDouble(int column)
> java.math.BigDecimal getBigDecimal(int column, int scale)
> java.math.BigDecimal getBigDecimal(int column)
> java.sql.Date getDate(int column)
> java.sql.Date getDate(int column, java.util.Calendar calendar)
> java.sql.Time getTime(int column)
> java.sql.Time getTime(int column, java.util.Calendar calendar)
> java.sql.Timestamp getTimestamp(int column)
> java.sql.Timestamp getTimestamp(int column, java.util.Calendar calendar)
> String getString(int column)
> byte[] getBytes(int column)
> java.io.InputStream getBinaryStream(int column)
> java.io.InputStream getAsciiStream(int column)
> java.io.InputStream getUnicodeStream(int column)
> java.io.Reader getCharacterStream(int column)
> java.sql.Blob getBlob(int column)
> java.sql.Clob getClob(int column)
> java.sql.Ref getRef(int column)
> java.sql.Array getArray(int column)
> Object getObject(int column)
> Object getObject(int column, java.util.Map map)
> final boolean getBoolean(String columnName)
> final byte getByte(String columnName)
> final short getShort(String columnName)
> final int getInt(String columnName)
> final long getLong(String columnName)
> final float getFloat(String columnName)
> final double getDouble(String columnName)
> final java.math.BigDecimal getBigDecimal(String columnName, int scale)
> final java.math.BigDecimal getBigDecimal(String columnName)
> final java.sql.Date getDate(String columnName)
> final java.sql.Date getDate(String columnName, java.util.Calendar cal)
> final java.sql.Time getTime(String columnName)
> final java.sql.Time getTime(String columnName, java.util.Calendar cal)
> final java.sql.Timestamp getTimestamp(String columnName)
> final java.sql.Timestamp getTimestamp(String columnName, java.util.Calendar 
> cal)
> final String getString(String columnName)
> final byte[] getBytes(String columnName)
> final java.io.InputStream getBinaryStream(String columnName)
> final java.io.InputStream getAsciiStream(String columnName)
> final java.io.InputStream getUnicodeStream(String columnName)
> final java.io.Reader getCharacterStream(String columnName)
> final java.sql.Blob getBlob(String columnName)
> final java.sql.Clob getClob(String columnName)
> final java.sql.Array getArray(String columnName)
> final java.sql.Ref getRef(String columnName)
> final Object getObject(String columnName)
> final Object getObject(String columnName, java.util.Map map)
> final java.sql.SQLWarning getWarnings()
> java.sql.ResultSetMetaData getMetaData()
> boolean isBeforeFirst()
> boolean isAfterLast()
> boolean isFirst()
> boolean isLast()
> int getFetchDirection()
> int getFetchSize()
> int getType()
> int getConcurrency()
> boolean rowUpdated()
> boolean rowInserted()
> boolean rowDeleted()
> void updateNull(String columnName)
> void updateBoolean(String columnName, boolean x)
> void updateByte(String columnName, byte x)
> void updateShort(String columnName, short x)
> void updateInt(String columnName, int x)
> void updateLong(String columnName, long x)
> void updateFloat(String columnName, float x)
> void updateDouble(String columnName, double x)
> void updateBigDecimal(String columnName, java.math.BigDecimal x)
> void updateDate(String columnName, java.sql.Date x)
> void updateTime(String columnName, java.sql.Time x)
> void updateTimestamp(String columnName, java.sql.Timestamp x)
> void updateString(String columnName, String x)
> void updateBytes(String columnName, byte x[])
> void updateBinaryStream(String columnName, java.io.InputStream x, int length)
> void updateAsciiStream(String columnName, java.io.InputStream x, int length)
> void updateCharacterStream(String columnName, java.io.Reader x, int length)
> void updateObject(String columnName, Object x, int scale)
> void updateObject(String columnName, Object x)
> Most of these races seem to be intentional, for instance, many (but not all) 
> of them contain the following comments:
>     // Live life on the edge and run unsynchronized
>     public boolean getBoolean(int column) {
>         ...
>         setWasNull(column);  // Placed close to the return to minimize risk 
> of thread interference
>         return result;
>     }
> But as I said above, it might be worth looking at these races because of 
> unexpected transformations that the JVM might do at the bytecode level.  
> Clearly, the second comment in the above method suggests that the programmer 
> assumes absence of aggressive transformations.
> ====================================================================
> Analysis of results of PASS 2:
> ====================================================================
> ===============================
> java.sql.Blob
> 0 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/blob/index.html
> ===============================
> java.sql.Clob
> 0 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/clob/index.html
> ===============================
> java.sql.Connection
> 51 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/connection/index.html
> These races are on fields so deep inside some class (java.nio.Buffer) that I 
> cannot verify them.  But synchronizing method nativeSQL in class Connection 
> on 'this' eliminates them.
> ===============================
> java.sql.ResultSet
> 51 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/result_set/
> Similar to the ones reported for Connection above.  Synchronizing method 
> getStatement in class ResultSet on connection_ eliminates them.
> ===============================
> java.sql.Statement
> 51 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/statement/index.html
> Similar to the ones reported for Connection above.  They are eliminated once 
> the races reported below for PreparedStatement are eliminated.
> ===============================
> java.sql.PreparedStatement
> 51 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/prepared_statement/index.html
> Similar to the ones reported for Connection above.  Synchronizing 10 methods 
> in class PreparedStatement on connection_ eliminates them:
> boolean execute(String sql)
> java.sql.ResultSet executeQuery(String sql)
> int executeUpdate(String sql)
> boolean execute(String sql, int autoGeneratedKeys)
> boolean execute(String sql, String[] columnNames) 
> boolean execute(String sql, int[] columnIndexes)
> int executeUpdate(String sql, int autoGeneratedKeys) 
> int executeUpdate(String sql, String[] columnNames)
> int executeUpdate(String sql, int[] columnIndexes)
> void setURL(int parameterIndex, java.net.URL x) 
> ===============================
> java.sql.DatabaseMetaData
> 3 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/database_metadata/index.html
> All are on field deferredException_ of class Agent, due to lack of 
> synchronization on connection_ in 34 methods in class DatabaseMetaData:
> boolean supportsSavepoints()
> String getURL()
> String getUserName()
> String getDatabaseProductName()
> String getDatabaseProductVersion()
> String getDriverName()
> String getDriverVersion()
> boolean supportsMixedCaseIdentifiers()
> boolean supportsMixedCaseQuotedIdentifiers()
> String getIdentifierQuoteString()
> boolean supportsColumnAliasing()
> boolean nullPlusNonNullIsNull()
> boolean supportsTableCorrelationNames()
> boolean supportsLikeEscapeClause()
> boolean supportsNonNullableColumns()
> boolean supportsMinimumSQLGrammar()
> boolean supportsANSI92EntryLevelSQL()
> boolean supportsSubqueriesInExists()
> boolean supportsSubqueriesInIns()
> boolean supportsSubqueriesInQuantifieds()
> boolean supportsCorrelatedSubqueries()
> java.sql.Connection getConnection()
> boolean supportsNamedParameters()
> boolean supportsMultipleOpenResults()
> boolean supportsGetGeneratedKeys()
> boolean supportsResultSetHoldability(int holdability)
> int getResultSetHoldability()
> int getDatabaseMajorVersion()
> int getDatabaseMinorVersion()
> int getJDBCMajorVersion()
> int getJDBCMinorVersion()
> int getSQLStateType()
> boolean locatorsUpdateCopy()
> boolean supportsStatementPooling()
> The races arise because all these methods call checkForClosedConnection 
> defined in the same class which in turn calls checkForDeferredExceptions in 
> class Agent:
>     void checkForDeferredExceptions() throws SqlException {
>         if (deferredException_ != null) {
>             SqlException temp = deferredException_;
>             deferredException_ = null;
>             throw temp;
>         }
>    }
> These races can be fixed by simply removing the call to 
> checkForClosedConnection in most of the above methods, instead of adding 
> synchronization to them.  In particular, this method doesn't call it:
>    // JDBC signature also does not throw SqlException, so we don't check for
>    // closed connection.
>     public int getDriverMajorVersion() {
>         return Version.getMajorVersion();
>     }
> Then why does a trivial method like this one call it?
>     public String getIdentifierQuoteString() throws SqlException {
>         checkForClosedConnection();
>         return "\"";
>     }
> Most of the above methods are trivial, simply returning a constant value.  Do 
> you really need to call checkForClosedConnection in them?
> ===============================
> java.sql.CallableStatement
> 3 races:
> http://suif.stanford.edu/~mhn/db-derby-10.1.1.0-src/classes/PASS2/callable_statement/index.html
> All are on field deferredException_ of class Agent, due to lack of 
> synchronization on connection_ in 52 methods in class CallableStatement (one 
> of which is shown below):
>     public java.net.URL getURL(String parameterName) throws SqlException {
>         if (agent_.loggingEnabled()) {
>             agent_.logWriter_.traceEntry(this, "getURL", parameterName);
>         }
>         super.checkForClosedStatement();
>         throw new SqlException(agent_.logWriter_, "JDBC 3 method called - not 
> yet supported");
>     }
> The above method calls checkForClosedStatement in class Statement which in 
> turn calls checkForDeferredExceptions in class Agent.

-- 
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.

Reply via email to