3
votes

After we have done some performance testing for our application which uses jackrabbit we faced with the huge problem with concurrent modification jackrabbit's repository. Problem appears when we add nodes or edit them in multithread emulation. Then I wrote very simple test which shows us that problem is not in our environment.

There is it:

Simple Stateless Bean


     @Stateless
        @Local(TestFacadeLocal.class)
        @Remote(TestFacadeRemote.class)
        public class TestFacadeBean implements TestFacadeRemote, TestFacadeLocal {
            public void doAction(int name) throws Exception {
                new TestSynch().doAction(name);
            }
        }

Simple class


    public class TestSynch {
        public void doAction(int name) throws Exception {
            Session session = ((Repository) new InitialContext().
                    lookup("java:jcr/local")).login(
                    new SimpleCredentials("username", "pwd".toCharArray()));
            List added = new ArrayList();
            Node folder = session.getRootNode().getNode("test");
            for (int i = 0; i <= 100; i++) {
                Node child = folder.addNode("" + System.currentTimeMillis(), 
                              "nt:folder");
                child.addMixin("mix:versionable");

                added.add(child);
            }
            // saving butch changes
            session.save();

            //checking in all created nodes
            for (Node node : added) {
                session.getWorkspace().getVersionManager().checkin(node.getPath());
            }
        }
    }

And Test class


    public class Test {
        private int c = 0;
        private int countAll = 50;
        private ExecutorService executor = Executors.newFixedThreadPool(5);

        public ExecutorService getExecutor() {
            return executor;
        }

        public static void main(String[] args) {
            Test test = new Test();
            try {
                test.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void start() throws Exception {
            long time = System.currentTimeMillis();
            TestFacadeRemote testBean = (TestFacadeRemote) getContext().
                                        lookup( "test/TestFacadeBean/remote");
            for (int i = 0; i < countAll; i++) {
                getExecutor().execute(new TestInstallerThread(i, testBean));
            }

            getExecutor().shutdown();
            while (!getExecutor().isTerminated()) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(c + " shutdown " + 
                               (System.currentTimeMillis() - time));

        }

        class TestInstallerThread implements Runnable {
            private int number = 0;
            TestFacadeRemote testBean;

            public TestInstallerThread(int number, TestFacadeRemote testBean) {
                this.number = number;
                this.testBean = testBean;
            }

            @Override
            public void run() {
                try {
                    System.out.println("Installing data " + number);
                    testBean.doAction(number);
                    System.out.println("STOP" + number);
                } catch (Exception e) {
                    e.printStackTrace();
                    c++;
                }
            }

        }

        public Context getContext() throws NamingException {
            Properties properties = new Properties();
            //init props
            ..............
            return new InitialContext(properties);
        }
    }

If I initialized executor with 1 thread in pool all done without any error. If I initialized executor with 5 thread I got sometimes errors:

on client

    java.lang.RuntimeException: javax.transaction.RollbackException: [com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted] [com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted] Can't commit because the transaction is in aborted state
        at org.jboss.aspects.tx.TxPolicy.handleEndTransactionException(TxPolicy.java:198)

on server at the beginning warn

    ItemStateReferenceCache [ItemStateReferenceCache.java:176] overwriting cached entry 187554a7-4c41-404b-b6ee-3ce2a9796a70

and then

    javax.jcr.RepositoryException: org.apache.jackrabbit.core.state.ItemStateException: there's already a property state instance with id 52fb4b2c-3ef4-4fc5-9b79-f20a6b2e9ea3/{http://www.jcp.org/jcr/1.0}created
        at org.apache.jackrabbit.core.PropertyImpl.restoreTransient(PropertyImpl.java:195) ~[jackrabbit-core-2.2.7.jar:2.2.7]
        at org.apache.jackrabbit.core.ItemSaveOperation.restoreTransientItems(ItemSaveOperation.java:879) [jackrabbit-core-2.2.7.jar:2.2.7]

We have tried synchronize this method and other workflow for handle multithread calls as one thread. Nothing helps.

And one more thing - when we have done similar test without ejb layer - all worked fine. It looks like container wrapped in own transaction and then all crashed.

Maybe somebody faced with such a problem. Thanks in advance.

1

1 Answers

5
votes

From the Jackrabbit Wiki:

The JCR specification explicitly states that a Session is not thread-safe (JCR 1.0 section 7.5 and JCR 2.0 section 4.1.2). Hence, Jackrabbit does not support multiple threads concurrently reading from or writing to the same session. Each session should only ever be accessed from one thread.

... If you need to write to the same node concurrently, then you need to use multiple sessions, and use JCR locking to ensure there is no conflict.