Search code examples
jdbcc3p0

Connection acquired from ComboPooledDataSource occasionally already checked-out


I'm finding that when multiple threads request a connection each from a single, shared instance of a ComboPooledDataSource, there is the occasion of if returning a connection from the pool that's already in-use. Is there a configuration setting or other means to make sure that connections currently checked-out aren't checked-out again?

package stress;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;

public class StressTestDriver
{
    private static final String _host = "";
    private static final String _port = "3306";
    private static final String _database = "";
    private static final String _user = "";
    private static final String _pass = "";

    public static void main(String[] args)
    {
        new StressTestDriver();
    }

    StressTestDriver()
    {
        ComboPooledDataSource cpds = new ComboPooledDataSource();

        try
        {
            cpds.setDriverClass( "com.mysql.jdbc.Driver" );

            String connectionString =  "jdbc:mysql://" + _host + ":" + _port + "/"
                    + _database;

            cpds.setJdbcUrl( connectionString );
            cpds.setMaxPoolSize( 15 );
            cpds.setMaxIdleTime( 100 );
            cpds.setAcquireRetryAttempts( 1 );
            cpds.setNumHelperThreads( 3 );
            cpds.setUser( _user );
            cpds.setPassword( _pass );
        }
        catch( PropertyVetoException e )
        {
            e.printStackTrace();
            return;
        }

        write("BEGIN");
        try
        {
            for(int i=0; i<100000; ++i)
                doConnection( cpds );
        }
        catch( Exception ex )
        {
            ex.printStackTrace();
        }
        finally
        {
            try
            {
                DataSources.destroy( cpds );
            }
            catch( SQLException e )
            {
                e.printStackTrace();
            }
        }
        write("END");
    }

    void doConnection( final ComboPooledDataSource cpds )
    {
        Thread[] threads = new Thread[ 10 ];
        final Set<String> set = new HashSet<String>(threads.length);

        Runnable runnable = new Runnable()
        {
            public void run()
            {
                Connection conn = null;

                try
                {
                    conn = cpds.getConnection();

                    synchronized(set)
                    {
                        String toString = conn.toString();

                        if( set.contains( toString ) )
                            write("In-use connection: " + toString);
                        else
                            set.add( toString );
                    }

                    conn.close();
                }
                catch( Exception e )
                {
                    e.printStackTrace();
                    return;
                }
            }
        };

        for(int i=0; i<threads.length; ++i)
        {
            threads[i] = new Thread( runnable );
            threads[i].start();
        }

        for(int i=0; i<threads.length; ++i)
        {
            try
            {
                threads[i].join();
            }
            catch( InterruptedException e )
            {
                e.printStackTrace();
            }
        }
    }

    private static void write(String msg)
    {
        String threadName = Thread.currentThread().getName();
        System.err.println(threadName + ": " + msg);
    }
}

Solution

  • I've run you stress test in my environment. It terminated without output.

    That said, it strikes me as a bit unreliable to use toString() output as a token for identity. The hashcodes encoded in Object.toString() are not guaranteed unique, and you're generating lots. You might just be seeing collisions.

    In particular, the scenario you are concerned about would represent a surprising and perplexing sort of bug. You should be seeing instances of com.mchange.v2.c3p0.impl.NewProxyConnection. Those instances are not checked in and out of the pool — they are single use, disposable proxies that wrap the underlying database Connection. They are constructed with new when you call getConnection(), remain associated with a PooledConnection object while you work, then dereferenced by the c3p0 library when you call close(), to be garbage collected after client code dereferences them. If somehow getConnection() was being called on the same underlying PooledConnection, that would be a c3p0 bug, and c3p0 would emit a warning to that effect. You are not seeing anything like...

    c3p0 -- Uh oh... getConnection() was called on a PooledConnection when it had already provided a client with a Connection that has not yet been closed. This probably indicates a bug in the connection pool!!!

    are you?

    I'd verify that you are not just seeing collisions of NewProxyConnection.toString(). Use a map rather than a set, hold a reference to close()ed Connection instances as values, when you see a collision of String keys, check with == whether the new Connection is the same instance as the value in your map. I'd be astonished if you find that they are the same instance. (I wish I could reproduce the problem to verify this on my own.)