Luiz,
Even I have faced the same problem.
This problem will come in concurrent request to repository to either add
content or read content.
This can be resolved using locks.
There are two cases
1. You can use java 5 concurrent.ReentrantReadWritelock, to lock the
repository and then unlock after session.save();
2. Make your nodes mix:lockable and then invoke node.lock() before you
do addNode and then invoke node.unlock() after session.save();
Regards,
Sunil Dhage
Luiz Fernando Teston wrote:
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));
}
}
}