0

We have a Spring based application and recently we went into production. We are using Spring @Controller that ultimately hit DAOs that use JDBCTemplate. It is using c3p0's ComboPooledDataSource

On an increased load (something like 150 concurrent users), the application hangs for all users - the DataSource gets locked by something - on a thread dump, there are like 200 threads that say - obviously the DataSource is deadlocked.

"http-bio-8080-exec-440" - Thread t@878
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Native Method)
- waiting on <146d984e> (a com.mchange.v2.resourcepool.BasicResourcePool)
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicResourcePool.java:1418)
at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(BasicResourcePool.java:606)
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:526)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutAndMarkConnectionInUse(C3P0PooledConnectionPool.java:756)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:683)
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:140)
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:573)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:637)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:666)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:674)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:718)

After that point the application becomes unusable unless restarted. When this happened DBA team didn't observe any load on the database.

At that time c3p0 was configured like that:

app_en.driverClass=com.mysql.jdbc.Driver
app_en.user=tapp_en
app_en.password=tapp_en
app_en.jdbcUrl=jdbc:mysql://10.10.0.102:3306/tapp_en?useUnicode=true&characterEncoding=utf-8&autoReconnect=true

app_en.acquireIncrement=5
app_en.maxIdleTime=3600
app_en.maxIdleTimeExcessConnections=300
app_en.unreturnedConnectionTimeout=3600
app_en.numHelperThreads=6
app_en.minPoolSize=20
app_en.maxPoolSize=100
app_en.idleConnectionTestPeriod=120
app_en.testConnectionOnCheckin=true

After that, I changed c3p0's configuration as follows - and enabled DEBUG logging for com.mchange.v2.c3p0 package:

app_en.driverClass=com.mysql.jdbc.Driver
app_en.user=tapp_en
app_en.password=tapp_en
app_en.jdbcUrl=jdbc:mysql://10.10.0.102:3306/tapp_en?    useUnicode=true&characterEncoding=utf-8&autoReconnect=true

app_en.acquireIncrement=5
app_en.maxIdleTime=180
app_en.maxIdleTimeExcessConnections=60
app_en.unreturnedConnectionTimeout=30
app_en.checkoutTimeout=10000
app_en.numHelperThreads=12
app_en.debugUnreturnedConnectionStackTraces=true
app_en.initialPoolSize=10
app_en.maxPoolSize=100
app_en.idleConnectionTestPeriod=120
app_en.preferredTestQuery="select 1 from tbl_users"

With this configuration in place, I again ran load tests and the application still hanged... although the threads recover after they are unable to obtain connection to the database. Even though, the game hanged for too many users even though the threads recovered unlike the previous configuration - so they had to restart their clients. Although all logging was enabled, the c3p0 logs don't log any deadlock messages. The error messages I see are just that:

[06/24/2015 12:20:54] [C3P0PooledConnectionPoolManager[identityToken->1oed6dl9a9ak8qsgqfvdu|4d6145af]-HelperThread-#10] DEBUG NewPooledConnection  - com.mchange.v2.c3p0.impl.NewPooledConnection@7f0bc55a closed by a client.
java.lang.Exception: DEBUG -- CLOSE BY CLIENT STACK TRACE
at com.mchange.v2.c3p0.impl.NewPooledConnection.close(NewPooledConnection.java:659)
at com.mchange.v2.c3p0.impl.NewPooledConnection.closeMaybeCheckedOut(NewPooledConnection.java:255)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.destroyResource(C3P0PooledConnectionPool.java:621)
at com.mchange.v2.resourcepool.BasicResourcePool$1DestroyResourceTask.run(BasicResourcePool.java:1024)
at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:696)

There aren't any transactions in the application made, nor are we using any TransactionManager or TransactionTemplate. I wonder if this may be some kind of bug in the frameworks used, or misconfiguration. These are the relevant frameworks used:

c3p0-0.9.5-pre8
mysql-connector-java-5.1.24
spring-core-3.2.1.RELEASE
spring-web-3.2.1.RELEASE
mchange-commons-java-0.2.7

We really appreciate any help because this is blocking our efforts to release our product.

P.S. EDIT: Here is the configuration of the DataSource:

<bean id="app_en_DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">
    <property name="driverClass" value="${app_en.driverClass}" />
    <property name="jdbcUrl" value="${app_en.jdbcUrl}" />
    <property name="user" value="${app_en.user}" />
    <property name="password" value="${app_en.password}" />

    <property name="acquireIncrement" value="${app_en.acquireIncrement}"></property>
    <property name="maxIdleTime" value="${app_en.maxIdleTime}"></property>
    <property name="maxIdleTimeExcessConnections" value="${app_en.maxIdleTimeExcessConnections}"></property>
    <property name="unreturnedConnectionTimeout" value="${app_en.unreturnedConnectionTimeout}"></property>
    <property name="checkoutTimeout" value="${app_en.checkoutTimeout}"></property>
    <property name="numHelperThreads" value="${app_en.numHelperThreads}"></property>
    <property name="debugUnreturnedConnectionStackTraces" value="${app_en.debugUnreturnedConnectionStackTraces}"></property>
    <property name="initialPoolSize" value="${app_en.initialPoolSize}"></property>
    <property name="maxPoolSize" value="${app_en.maxPoolSize}"></property>
    <property name="idleConnectionTestPeriod" value="${app_en.idleConnectionTestPeriod}"></property>
    <property name="preferredTestQuery" value="${app_en.preferredTestQuery}"></property>
</bean>

And here is some code inside the application that isn't using the jdbcTemplate directly. There is nothing else that does that, everything else is jdbcTemplate.update, jdbcTemplate.query:

    Connection conn = null;
    ResultSet getItemsRS = null;

    try {
        JdbcTemplate jdbcTemplate = getJdbcTemplate(database);

        conn = jdbcTemplate.getDataSource().getConnection();

        UserItems items;

        if (!action.areItemsNew()) {

            conn.setAutoCommit(false);
            conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

            PreparedStatement getItemsPS = conn.prepareStatement("select * from tbl_items where ownerId = ? for update",
                    ResultSet.TYPE_FORWARD_ONLY,
                    ResultSet.CONCUR_UPDATABLE);
            getItemsPS.setLong(1, userId);

            getItemsRS = getItemsPS.executeQuery();
            getItemsRS.next();

            items = new UserItemsRowMapper().mapRow(getItemsRS, getItemsRS.getRow());
        } else {
            items = new UserItems();
        }

        action.doUserItemsAction(items);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(items.getItemContainers());
        oos.close();
        byte[] data = baos.toByteArray();
        Blob blob = conn.createBlob();
        blob.setBytes(1, data);

        if (!action.areItemsNew()) {
            getItemsRS.updateBlob("data", blob);
            getItemsRS.updateRow();
        } else {
            jdbcTemplate.update("insert into tbl_items(ownerId,data) values(?,?)", userId, data);
        }

    } catch (Exception e) {
        logger.error(e);
        throw new RuntimeException(e);
    } finally {
        if (!action.areItemsNew()) {
            try {
                conn.commit();
                conn.close();
            } catch (SQLException e) {
                logger.error(e);
                throw new RuntimeException(e);
            }
        }
    }

The reason for this code is that I would like to block reading/writing to the user's items before they are updated by this operation action.doUserItemsAction(items) as written above.

Martin Asenov
  • 1,227
  • 2
  • 16
  • 37
  • checkout these links http://stackoverflow.com/questions/17272141/websphere-hangs-due-to-c3p0 http://stackoverflow.com/questions/14105932/c3p0-hangs-in-awaitavailable-with-hibernate http://stackoverflow.com/questions/19313103/c3p0-hangs-java-1-6 – Sarfaraz Khan Jun 24 '15 at 10:27
  • sorry but those links don't give me any more insight into the problem that I already have... – Martin Asenov Jun 24 '15 at 10:33
  • 1
    Where is the code and configuration? Also I would suggest to use another pool and not C3P0, something like [HikariCP](https://github.com/brettwooldridge/HikariCP). – M. Deinum Jun 24 '15 at 10:40
  • @M.Deinum the code is too much to be posted here. it is basically doing jdbcTemplate.query, jdbcTemplate.update. There is only 1 thing that is quite different, and it is reading, locking, writing to and releasing a blob, using the DataSource directly, with autoCommit=false and select for update. I can paste that here if you like. Other stuff is just trivial CRUD operations – Martin Asenov Jun 24 '15 at 10:49
  • 1
    If you are doing updates you will need a transaction manager especially if you do multiple modifications, else you might leave the database in an inconsistent state. Also the writing of a blob is interesting as that looks like you are messing around with a connection yourself. Also I don't want all the code at least something that might lead to the issue (code where you are using/obtaining a plain connection for instance) and the configuration you use. – M. Deinum Jun 24 '15 at 10:52
  • @M.Deinum edited the question with the information – Martin Asenov Jun 24 '15 at 10:59
  • That is one ugly contraption, why aren't you just using Spring, you are trying very hard to work around it... I strongly suggest using either declarative transaction management or at least a `TransactionTemplate` to let spring manage the transactions and connections. You should always close the connection you are only closing it in certain situations, this leaves them open hence a connection leak. – M. Deinum Jun 24 '15 at 11:10
  • Another thing if performance is an issue you shouldn't create a `JdbcTemplate` each time you need one, it is thread safe so just use a single one and share it. Create one in your context and inject it. – M. Deinum Jun 24 '15 at 11:11
  • The reason for that was that I was unable to achieve the required result using a TransactionManager - the row was still open for reading and writing while in the transaction block. That's why I came to this solution, because it did lock the row. Do you think this may lead to the problem? Also would you still suggest moving to HikaruCP? – Martin Asenov Jun 24 '15 at 11:14
  • Yes as that is a faster (and more recent) pool then c3p0. – M. Deinum Jun 24 '15 at 11:14
  • Could you post the full method? As there seem to be parts missing. – M. Deinum Jun 24 '15 at 11:15
  • @M.Deinum this is the full method that does things other than jdbcTemplate operations. Besides, jdbcTemplate is `@Autowired`, so it is reused and managed by Spring – Martin Asenov Jun 24 '15 at 11:20

2 Answers2

1

So, a few things.

1) The "error" messages that you see are not errors, when c3p0 logs an Exception whose message begins with DEBUG, that means you are logging at DEBUG levels and c3p0 has generated the Exception just to capture the stack trace. (c3p0 is an old library; Thread.getStackTrace() didn't exist back in the day, creating an Exception was a convenient way to capture and dump the stack.) You are just logging the expected destruction of pooled Connections due to expiration or test failures. In general, c3p0 expects to log at INFO, it will be very verbose at DEBUG levels.

2) You are not deadlocking c3p0's Thread pool. If you were, you'd see APPARENT DEADLOCK messages and then recoveries. You are experiencing a condition of pool exhaustion: clients are awaiting Connections, but the pool is at maxPoolSize and unable to acquire them.

3) The usual cause of pool exhaustion is a Connection leak: Somewhere in your application's code path, under some (probably Exceptional) circumstances, Connections are acquired and then never close()ed. You need to be very careful to ensure that Connections are reliably close()ed in finally blocks in ways that cannot be skipped due to prior failures within the finally block. In Java 7+, use try-with-resources. In older versions, use the reliable resource cleanup idiom.

4) To test whether a Connection leak is the issue, set the c3p0 config params unreturnedConnectionTimeout and debugUnreturnedConnectionStackTraces. unreturnedConnectionTimeout will work around the problem, but yuck. More importantly, debugUnreturnedConnectionStackTraces will show you where the problem is so that you can fix it, logging the stack trace that opened the unclosed Exception at INFO. (You must set unreturnedConnectionTimeout for debugUnreturnedConnectionStackTraces to have any effect; the stack trace is logged when a Connection times out as abandoned.)

5) Although 0.9.5-pre8 is probably ok, the current production version of c3p0 is c3p0-0.9.5.1 (which depends upon mchange-commons-java v.0.2.10). You might think about using that. I don't think it has anything at all to do with your issue, but still.

I hope this helps!

Update: Since you've now posted code that shows the likely Connection leak, here's a suggestion for how to fix it. Replace your finally block with:

} finally {
    if ( conn != null ) {
        try { if (!action.areItemsNew()) conn.commit(); }
        catch (SQLException e) {
           logger.error(e);
           throw new RuntimeException(e);
        } finally {
           conn.close()
        }
    }
}

Update 2: The redone finally block above will solve the Connection leak, but if I were you I'd also change the logic of this code regarding commit(). Here's a suggested revision:

Connection conn = null;
ResultSet getItemsRS = null;

try {
    JdbcTemplate jdbcTemplate = getJdbcTemplate(database);

    conn = jdbcTemplate.getDataSource().getConnection();

    UserItems items;

    if (!action.areItemsNew()) {

        conn.setAutoCommit(false);
        conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

        PreparedStatement getItemsPS = conn.prepareStatement("select * from tbl_items where ownerId = ? for update",
                ResultSet.TYPE_FORWARD_ONLY,
                ResultSet.CONCUR_UPDATABLE);
        getItemsPS.setLong(1, userId);

        getItemsRS = getItemsPS.executeQuery();
        getItemsRS.next();

        items = new UserItemsRowMapper().mapRow(getItemsRS, getItemsRS.getRow());
    } else {
        items = new UserItems();
    }

    action.doUserItemsAction(items);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(items.getItemContainers());
    oos.close();
    byte[] data = baos.toByteArray();
    Blob blob = conn.createBlob();
    blob.setBytes(1, data);

    if (!action.areItemsNew()) {
        getItemsRS.updateBlob("data", blob);
        getItemsRS.updateRow();
        conn.commit();
    } else {
        jdbcTemplate.update("insert into tbl_items(ownerId,data) values(?,?)", userId, data);
    }
} catch (Exception e) {
    logger.error(e);
    throw new RuntimeException(e);
} finally {
    try { if ( conn != null ) conn.close(); }
    catch ( Exception e )
      { logger.error(e); }
}

Now commit() will get called only if (!action.areItemsNew()) AND all expected operations have succeeded. Before commit() would get called even if something went wrong. The resource cleanup code is much simpler and cleaner too. Note that in the suggested version, if there's an Exception on close() it's logged, but it is not wrapped and rethrown as a RuntimeException. Usually if there's an Exception on close(), there will have been a more informative Exception prior and that's the one you want to see. If the only place an Exception occurs is on close(), it means that all database operations have succeeded so your application can proceed correctly despite the fault. (If there are lots of Exceptions on close(), eventually you'd exhaust the Connection pool, but in practice that would happen only if there's something badly wrong with your database or network.)

Steve Waldman
  • 12,904
  • 1
  • 29
  • 42
  • I already set those two parameters - unreturnedConnectionTimeout and debugUnreturnedConnectionStackTraces, as I wrote in the question. It didn't give me any insight, other than forcing stuck connections into release. No error was logged by c3p0 even with debugUnreturnedConnectionStackTraces, I've enabled DEBUG logging for `com.mchange.v2.c3p0` package. – Martin Asenov Jun 24 '15 at 11:17
  • Oh. So the problem is pretty obvious in the code that you added while I wrote: There are two ways a Connection could fail to get close()ed: (1) ` if (!action.areItemsNew())` you simply neglect to close the Connection. (2) even if that condition is true, if `commit()` throws an Exception, your call to `close()` will be skipped. This code is very likely to leak Connections over time and use. – Steve Waldman Jun 24 '15 at 11:17
  • Log at INFO to see `debugUnreturnedConnectionStackTraces`; they'll be lost in too much noise if you indiscriminately log at DEBUG levels. That you observed "forcing stuck connections into release" verifies the problem. There are stuck, stranded Connections, because under certain circumstances the application fails to `close()` Connections. Fix that and your life will be much better! – Steve Waldman Jun 24 '15 at 11:20
  • I've added a suggested fix of your finally block as an update to the post. Note that if the Connection has been successfully acquired (and so is non-null), in the new version there is no code path that won't pass through `conn.close()`. – Steve Waldman Jun 24 '15 at 11:31
  • Thank you. Do you think manually messing with connections, instead of delegating that to Spring and a TransactionTemplate may be a problem, if connections are closed properly on my side? – Martin Asenov Jun 24 '15 at 11:35
  • 1
    As far as c3p0 is concerned, there is no problem at all with manually managing your Connections as long as you don't leak them. Some people may find it convenient to delegate Connection management to middleware on the (not always reliable) theory that the middleware will take care of this stuff for them and get things right. My own preference is to manage Connections directly and explicitly. It's not really so hard to do. – Steve Waldman Jun 24 '15 at 11:38
  • This is unrelated to your specific problem, but if I were you, I'd take the `commit()` call out of the finally block entirely. You don't really want to commit if there has been a failure in the try. – Steve Waldman Jun 24 '15 at 11:46
  • 1
    I've added a suggested reworking of your code that places the `commit()` in the try block and dramatically simplified the `finally` block. – Steve Waldman Jun 24 '15 at 11:58
  • The problem with your approach, as I see, is that you might use more then a single connection, where as you want to have a single one. Also manually managing your connections is generally not preferred (but that is imho). managing resources is error prone and it is quite easy to forget a close or write a wrong try/catch/finally leaking connections. Using the `JdbcTemplate` correctly and have proper transaction management in place lets you write your business code and focus on that instead of handling resources. – M. Deinum Jun 24 '15 at 12:05
  • @M.Deinum As you say, opinions vary about the use of middleware. I don't much use Spring or JdbcTemplate at all, so I won't comment on that specifically. I do manage my own Connections, and don't find that challenging at all. In the code above, there is no hazard that multiple Connections will come to be. One Connection is checked out of the datasource, there can be at most one Connection that needs to be closed. – Steve Waldman Jun 24 '15 at 12:10
  • Well no, that is the connection that is checked out, however the JdbcTemplate.update uses another connection. So in a bad case you have 2 connections (although the latter is closed by the template). I find error handling code quite tedious to write and hate to sloppy/paste try/catch/finally blocks just for closing. In that way the `JdbcTemplate` made my life a lot easier :). – M. Deinum Jun 24 '15 at 12:12
  • 1
    @M.Deinum Yes, you are right, I missed that, there is a call that delegates management of the database to JdbcTemplate. As you say, from a Connection management perspective that's not a problem, since the second Connection will be managed internally by JdbcTemplate. People's preferences about the level at which they like to manage things differ, we needn't agree about that. If you find delegating things to JdbcTemplate helpful, that's great. We might agree it's a bit awkward to go half and half, to manage one Connection directly but conditionally delegate management of a second Connection. – Steve Waldman Jun 24 '15 at 12:19
1

The code you have is potentially dangerous and has a connection leak, when checking out a connection yourself you should always close it, there might be a case it fails to close the connection.

Instead I strongly suggest using Spring to manage your transactions and connections.

First annotate your method with @Transactional(isolation=SERIALIZABLE). Next add a DataSourceTransactionManager and <tx:annotation-driven /> to your configuration. After these changes rewrite the data access code you have.

JdbcTemplate jdbcTemplate = getJdbcTemplate(database);
final UserItems items;
if (!action.areItemsNew()) {
    items = jdbcTemplate.queryForObject("select * from tbl_items where ownerId = ? for update", userId, new UserItemsRowMapper());
} else {
    items = new UserItems();
}

action.doUserItemsAction(items);

String query = !action.areItemsNew() ? "update tbl_items set data=? where ownerId=?" : "insert into tbl_items(data,ownerId) values(?,?)";

byte[] data = SerializationUtils.serialize(items.getItemContainers());
jdbcTemplate.update(query, new SqlLobValue(data), userId);

Something like that (together with the aformentioned modification should work). (This was more or less from the top of my head, so it might need some tweaking). The use of proper transaction management ensures that everything is reusing the same single connection instead of multiple connections, it also ensures that a connection is returned to the pool when finished or when something goes wrong.

I would still suggest a different datasource as C3P0 is pretty old.

M. Deinum
  • 94,295
  • 20
  • 185
  • 191
  • @do you think this piece of code will ensure the same effect (locking the row before method is finished? Thank you! – Martin Asenov Jun 24 '15 at 12:14
  • 1
    Yes, due to the transactional demarcation and the select for update. – M. Deinum Jun 24 '15 at 12:15
  • Thank you. Will try that right away! I hope this solves the problem – Martin Asenov Jun 24 '15 at 12:16
  • You might not need `SERIALIZABLE` but it might be that the default is enough (as you lock the row with the `select ... for update`). – M. Deinum Jun 24 '15 at 12:17
  • There are unhandled exceptions by this method. What would be the best way to handle them, given the fact this is supposed to execute in a transactional block? – Martin Asenov Jun 24 '15 at 12:21
  • 1
    Those probably stem from the fact that you are using the output stream to create a `byte[]` (or calling the close method). Wrap that in a try/catch block and rethrow the `IOException` as a `RuntimeException`. – M. Deinum Jun 24 '15 at 12:24
  • Just updated the answer to make it easier, you should not have to catch exceptions anymore. – M. Deinum Jun 24 '15 at 12:29
  • Interestingly enough, upon launch I get `org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 128 in XML document from ServletContext resource [/WEB-INF/spring-config.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 128; columnNumber: 26; cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'tx:annotation-driven'.` On that line there is ``. The correct namespace has been specified though – Martin Asenov Jun 24 '15 at 12:31
  • Just slapping that in your configuration isn't going to work, you of course have to update your xml namespaces accordingly. – M. Deinum Jun 24 '15 at 12:32
  • this code comes with a RuntimeException of `java.lang.IllegalArgumentException: SqlLobValue only supports SQL types BLOB and CLOB` – Martin Asenov Jun 24 '15 at 13:15
  • And you have copied the code? I did modify the insert query as I switch the parameters around... – M. Deinum Jun 24 '15 at 13:20
  • It comes up with the same error even with the `SerilizationUtils` stuff – Martin Asenov Jun 24 '15 at 13:23
  • That isn't related to the `SerilizationUtils` it is related to the detection of the sql type of the column. For some reason it doesn't detect the blob column as a `java.sql.Types.BLOB`. Is it really a blob or a `LONGBLOB` for instance? – M. Deinum Jun 24 '15 at 13:25
  • No wrapping of `data` into `new SqlLobValue()` did the trick ! – Martin Asenov Jun 24 '15 at 13:27
  • Oh maybe I missed that. Thank you anyway, now I'll check if the connection leak is still present or not – Martin Asenov Jun 24 '15 at 13:30