spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
56.51k stars 38.11k forks source link

Spring 1.2.6 version of DataSourceUtils.suspend() method causes an IllegalArgumentException to be thrown when using inner transaction with PROPOGATION_REQUIRES_NEW. [SPR-1539] #6238

Closed spring-projects-issues closed 12 years ago

spring-projects-issues commented 18 years ago

William Parker opened SPR-1539 and commented

DataSourceUtils.suspend() has been changed in version 1.2.6 to release a connection on suspend if the application doesn't keep a handle to it anymore. If working with more than one connection in a transaction this can cause problems with the TransactionSynchronizationManager in the AbstractPlatformTransactionManager.suspend(Object) method.

If you have two transactions running, one within the other with a propogation of "PROPOGATION_REQUIRES_NEW", then when the inner transaction runs, the connections in the suspendedSynchronizations list within the AbstractPlatformTransactionManager.suspend(Object) are released on suspension in DataSourceUtils.suspend(), (since 1.2.6). If the inner transaction is running within a loop then on the second run of the loop, the AbstractPlatformTransactionManager.suspend(Object) will try to release the connections again using DataSourceUtils.suspend(), resulting in IllegalArgumentException : Active Connection is required.

The following pseudo-code below can reproduce the exception.

    // Set up two Data Sources to two different databases....

    LazyConnectionDataSourceProxy dataSourceA = ................
    ..............
    ...............

    LazyConnectionDataSourceProxy dataSourceB = ................
    ..............
    ...............

    // Set up a Jdbc template for each Datasource....
    final JdbcTemplate jdbcTemplateA = new JdbcTemplate(dataSourceA);
    final JdbcTemplate jdbcTemplateB = new JdbcTemplate(dataSourceB);

    // Set up Transaction Template on dataSourceA with PROPAGATION_REQUIRES_NEW....

    DataSourceTransactionManager dstm = new DataSourceTransactionManager(dataSourceA);
    ptm.afterPropertiesSet();           
    final TransactionTemplate transactionTemplate = new TransactionTemplate();
    transactionTemplate.setTransactionManager(dstm);
    transactionTemplate.setPropagationBehavior( TransactionTemplate.PROPAGATION_REQUIRES_NEW );
    transactionTemplate.afterPropertiesSet();

    // Execeute outer transaction.........
    transactionTemplate.execute( new TransactionCallbackWithoutResult() {
        public void doInTransactionWithoutResult(TransactionStatus status) {

                                      Execute a query using both connections.........                         
    jdbcTemplateA.execute( "select top 1 * from TableA"); 
                                       jdbcTemplateB.execute( "select top 1 * from TableB" );

                                      // Loop round inner transaction - exception is thrown in second loop............
                                 for (int j=0; j < 2; j++) {                
                         transactionTemplate.execute( new TransactionCallbackWithoutResult() {
                    public void doInTransactionWithoutResult(TransactionStatus status) {}
                });
            }
        }       
    } );

Affects: 1.2.6

spring-projects-issues commented 18 years ago

Juergen Hoeller commented

Thanks for the report! This is an unfortunate bug that slipped into 1.2.6... It's easy enough to fix, though:

Patch Spring 1.2.6's DataSourceUtils, line 339, to the following:

if (this.connectionHolder.hasConnection() && !this.connectionHolder.isOpen()) {

I.e. add the this.connectionHolder.hasConnection() check there.

Call "build alljars" in the root of your Spring distribution to generate updated jars.

The upcoming Spring 2.0 M1 release (today) will contain this fix, as well as a likely 1.2.7 update release (in January).

Juergen

spring-projects-issues commented 18 years ago

Gregory Bohmer commented

I think I might be experiencing a similar issue (no nested Transaction though) but am not sure if it is caused by the same fix.

Start TX1 Access DataSourceX Call service Z that is TX_NOT_SUPPORTED, which causes TX1 to be suspended. Complete Z service call and get result, which should cause TX1 to be resumed. Access same DataSourceX. Call service Z again that is TX_NOT_SUPPORTED, which will cause TX1 to be suspended again. This bombs with IllegalArgumentException: Active Connection is required.

Strange thing though is that if I try it again (brand new TX2) after failing, it will not bomb and all is good. :-(

spring-projects-issues commented 18 years ago

Juergen Hoeller commented

This sounds like an issue with the same root cause, so it should be fixed in 2.0 M1 as well.

Could you please give 2.0 M1or a recent 2.0 M2 nightly snapshot a try and let me know whether it works for you there?

FYI, we'll release a 1.2.7 release any day now, among other things containing this fix. It would be good to verify your scenario before, though.

Juergen

spring-projects-issues commented 18 years ago

Reinhard Hoepner commented

I could only get rid of this problem, when i completely disabled the release(...) on the connection holder

public void suspend() {
     if (this.holderActive) {
          TransactionSynchronizationManager.unbindResource(this.dataSource);

// if (this.connectionHolder.hasConnection() && !this.connectionHolder.isOpen()) { // // Release Connection on suspend if the application doesn't keep // // a handle to it anymore. We will fetch a fresh Connection if the // // application accesses the ConnectionHolder again after resume, // // assuming that it will participate in the same transaction. // releaseConnection(this.connectionHolder.getConnection(), this.dataSource); // this.connectionHolder.setConnection(null); // } } }

because it blows up the number of database connections i might possably use in one jta transaction.

If i have a sequence like this

1) REQUIRES_NEW 2) REQUIRED 3) NOT_SUPPORTED 3) REQUIRED

the database operations performed during 4. have no chance to run on the same database connection like 2. (JDBCTemplate will always release referenceCount to 0 in ResourceHolderSupport). This results in a new database connection participating in the jta transaction. It might be commited (depends on the jta implementation, oracle does, jotm does not) as it will be enlisted as XAResource in the javax.transaction.Transaction, but who wants to commit lot's of database connections, if one would be sufficient. Not talking about isolation...

By the way, great framework!

Reinhard