[jboss-user] [JBoss Cache: Core Edition] - Deadlock in JDBCCacheLoader
gpklimek
do-not-reply at jboss.com
Thu Jan 8 12:49:03 EST 2009
It is possible to have a deadlock in JDBCCacheLoader when concurrently trying to put two different entries into it. Timing has to be right of course and even then chances are small (like 1 to 16384). But still it is a problem.
I have found it in version 1.4.1.SP8 but it is still a problem in 3.0.1.GA
I have put together a unit test (for 3.0.1.GA) which demonstrates the issue: trying to put Fqns "/a/b" and "/c/d" at the same time is fine, but "/a/m" and "/l/p" is not
| import static org.easymock.EasyMock.*;
|
| import java.sql.Connection;
| import java.sql.DatabaseMetaData;
| import java.sql.PreparedStatement;
| import java.sql.ResultSet;
| import java.util.Hashtable;
| import java.util.Properties;
|
| import javax.naming.Context;
| import javax.naming.NamingException;
| import javax.naming.spi.InitialContextFactory;
| import javax.sql.DataSource;
|
| import junit.framework.TestCase;
|
| import org.easymock.Capture;
| import org.easymock.IAnswer;
| import org.jboss.cache.CacheSPI;
| import org.jboss.cache.Fqn;
| import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
| import org.jboss.cache.io.ByteBuffer;
| import org.jboss.cache.loader.JDBCCacheLoader;
| import org.jboss.cache.marshall.Marshaller;
|
| public class JDBCCacheLoaderTest extends TestCase {
| private static final long EXECUTE_QUERY_DELAY = 1000;
| private static final long MAX_TIME_FOR_PUT = 10000;
|
| private static Context context;
|
| public static class SimpleContextFactory implements InitialContextFactory {
| public Context getInitialContext(Hashtable<?, ?> environment) throws NamingException {
| return context;
| }
| }
|
| private JDBCCacheLoader jdbcCacheLoader;
|
| @Override
| protected void setUp() throws Exception {
| System.setProperty("java.naming.factory.initial", "JDBCCacheLoaderTest$SimpleContextFactory");
|
| //JDBC mocks
| context = createNiceMock("context", Context.class);
| final CacheSPI cacheSPI = createNiceMock("cacheSPI", CacheSPI.class);
| final Marshaller marshaller = createNiceMock("marshaller", Marshaller.class);
| final DataSource dataSource = createNiceMock("dataSource", DataSource.class);
| final Connection connection = createNiceMock("connection", Connection.class);
| final DatabaseMetaData databaseMetaData = createNiceMock("databaseMetaData", DatabaseMetaData.class);
| final PreparedStatement preparedStatement = createNiceMock("preparedStatement", PreparedStatement.class);
| final ResultSet resultSet = createNiceMock("resultSet", ResultSet.class);
| makeThreadSafe(cacheSPI, true);
| makeThreadSafe(marshaller, true);
| makeThreadSafe(dataSource, true);
| makeThreadSafe(connection, true);
| makeThreadSafe(databaseMetaData, true);
| makeThreadSafe(preparedStatement, true);
| makeThreadSafe(resultSet, true);
|
| //mock expectations
| expect(context.lookup("java:/JDBCCacheLoader")).andStubReturn(dataSource);
| expect(cacheSPI.getMarshaller()).andStubReturn(marshaller);
| expect(marshaller.objectToBuffer(capture(new Capture<Object>()))).andStubReturn(new ByteBuffer(new byte[] {'a'}, 0, 1));
| expect(dataSource.getConnection()).andStubReturn(connection);
| expect(connection.getMetaData()).andStubReturn(databaseMetaData);
| expect(databaseMetaData.getDriverName()).andStubReturn("a driver name");
| expect(connection.prepareStatement(capture(new Capture<String>()))).andStubReturn(preparedStatement);
| expect(preparedStatement.executeQuery()).andStubAnswer(new IAnswer<ResultSet>() {
| public ResultSet answer() throws Throwable {
| Thread.sleep(EXECUTE_QUERY_DELAY);
| return resultSet;
| }
| });
| preparedStatement.executeUpdate();
| expectLastCall().andStubReturn(1);
|
| replay(context, cacheSPI, marshaller, dataSource, connection, databaseMetaData, preparedStatement, resultSet);
|
| //JDBCCacheLoader init
| Properties props = new Properties();
| props.put("cache.jdbc.table.name","TREE_CACHE");
| props.put("cache.jdbc.table.create","false");
| props.put("cache.jdbc.fqn.column","FQN");
| props.put("cache.jdbc.node.column","NODE");
| props.put("cache.jdbc.datasource","java:/JDBCCacheLoader");
|
| IndividualCacheLoaderConfig config = new IndividualCacheLoaderConfig();
| config.setProperties(props);
|
| jdbcCacheLoader = new JDBCCacheLoader();
| jdbcCacheLoader.setConfig(config);
| jdbcCacheLoader.setCache(cacheSPI);
| jdbcCacheLoader.start();
| }
|
| public void testPutCaseDeadlock() throws Exception {
| Thread thread1 = new Thread(new Putter("/a/m"));
| Thread thread2 = new Thread(new Putter("/l/p"));
| thread1.start();
| thread2.start();
| thread1.join(MAX_TIME_FOR_PUT);
| thread2.join(MAX_TIME_FOR_PUT);
| assertEquals(Thread.State.TERMINATED, thread1.getState());
| assertEquals(Thread.State.TERMINATED, thread2.getState());
| }
|
| public void testPutCaseNoDeadlock() throws Exception {
| Thread thread1 = new Thread(new Putter("/a/b"));
| Thread thread2 = new Thread(new Putter("/c/d"));
| thread1.start();
| thread2.start();
| thread1.join(MAX_TIME_FOR_PUT);
| thread2.join(MAX_TIME_FOR_PUT);
| assertEquals(Thread.State.TERMINATED, thread1.getState());
| assertEquals(Thread.State.TERMINATED, thread2.getState());
| }
|
| private class Putter implements Runnable {
| private String name;
|
| public Putter(String name) {
| this.name = name;
| }
|
| public void run() {
| try {
| jdbcCacheLoader.put(Fqn.fromString(name), "a key", "a value");
| } catch (Exception e) {
| e.printStackTrace();
| }
| }
| }
| }
|
View the original post : http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4200407#4200407
Reply to the post : http://www.jboss.com/index.html?module=bb&op=posting&mode=reply&p=4200407
More information about the jboss-user
mailing list