Hi,
I just found an strange behavior while using multiple sessions on multi
thread environment. I'd like to know if I did something wrong.
My application code do this:
1 - before start to work, it creates a root node inside a new session. It
saves and close this session.
2 - create a bunch of workers. Each worker has its own session.
3 - each worker adds nodes inside the root node retrieved by its session. It
saves and do a logout properly.
4 - fill this workers on a list of Callables to be passed to an java 5
ThreadPoolExecutor.
5 - it executes all workers on the ThreadPool.
6 - it do some asserts to verify if it gots some errors or not.
During phase 5 it gots an exception telling this:
javax.jcr.InvalidItemStateException: Item cannot be saved because it has
been modified externally: node /
at org.apache.jackrabbit.core.ItemImpl.getTransientStates(ItemImpl.java:245)
at org.apache.jackrabbit.core.ItemImpl.save(ItemImpl.java:939)
at org.apache.jackrabbit.core.SessionImpl.save(SessionImpl.java:915)
I just wrote a test to simulate this behavior. Sometimes it got this error
sometimes not. Also to be easy to simulate this error, just change the
THREAD_SIZE and NODE_SIZE variables.
I'm very thankful for your help!
Fernando Teston
---- test code -----
package org.openspotlight.jcr.provider.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.jcr.Node;
import javax.jcr.Session;
import org.junit.Assert;
import org.junit.Test;
public class MultithreadedSessionTest {
enum Status {
OK, ERROR
}
class Worker implements Callable<Status> {
private final Session session;
private final int i;
public Worker(final Session session, final int i) {
this.session = session;
this.i = i;
}
public Status call() throws Exception {
try {
Node parent1 = session.getRootNode().getNode("root");
for (int j = 0; j < NODES_SIZE; j++) {
parent1 = parent1.addNode("node_" + i + "_" + j);
}
session.save();
session.logout();
return Status.OK;
} catch (final Exception e) {
e.printStackTrace();
return Status.ERROR;
}
}
}
private final int THREAD_SIZE = 100;
private final int NODES_SIZE = 10;
private Session openSession() {
// TODO - here opens a new session
}
@Test
public void shouldInsertNodesInParallel() throws Exception {
final Session s = openSession();
s.getRootNode().addNode("root");
s.save();
s.logout();
final List<Callable<Status>> workers = new ArrayList<Callable<Status>>(
THREAD_SIZE);
for (int i = 0; i < THREAD_SIZE; i++) {
final Session session = openSession();
workers.add(new Worker(session, i));
}
final ExecutorService threadPool = Executors.newFixedThreadPool(4);
final List<Future<Status>> resultList = threadPool.invokeAll(workers);
for (final Future<Status> result : resultList) {
Assert.assertTrue(result.get().equals(Status.OK));
}
}
}