You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@jackrabbit.apache.org by Nicolas Belisle <Ni...@bibl.ulaval.ca> on 2005/07/06 20:42:16 UTC

Problems with concurrent sessions

Hi,

I'm a Jackrabbit newbie. I'm doing some tests with the tool and I'm having 
problem with concurrent sessions.

I have joined my (simple) test class (JCRTest) and the exception I get from 
running it.

Anyone had similar problems ?

Regards,

Nicolas


-->The exception report:
java.util.NoSuchElementException: cdeab285-fdbc-4af3-918a-bf4316a29276
at org.apache.jackrabbit.core.LazyItemIterator.next(LazyItemIterator.java:157)
at 
org.apache.jackrabbit.core.LazyItemIterator.nextNode(LazyItemIterator.java:98)
at app.JCRTest$ThreadDeleter.run(JCRTest.java:107)

-->The class:
package app;

import java.util.Hashtable;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;
import javax.naming.Context;
import javax.naming.InitialContext;

import org.apache.jackrabbit.core.jndi.RegistryHelper;
import org.apache.jackrabbit.core.value.StringValue;

public class JCRTest {

         private Repository repository;

         public JCRTest(Repository repository) {
                 this.repository = repository;
         }

         public void execute() throws Exception {
                 Session session = this.repository.login(new 
SimpleCredentials("", "".toCharArray()), null);

                 Node rn = session.getRootNode();
                 new ThreadObserver().start();
                 Node n = rn.addNode("node" + System.currentTimeMillis(), 
"nt:unstructured");
                 n.setProperty("testprop", new StringValue("Hello, World."));
                 new ThreadDeleter().start();

                 session.save();
                 session.logout();
         }

         public static void main(String[] args) {
                 try {
                         String configFile = "repository/repository.xml";
                         String repHomeDir = "repository";

                         Hashtable env = new Hashtable();
                         env.put(Context.INITIAL_CONTEXT_FACTORY, 
"org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory");
                         env.put(Context.PROVIDER_URL, "localhost");
                         InitialContext ctx = new InitialContext(env);

                         RegistryHelper.registerRepository(ctx, "repo", 
configFile, repHomeDir, true);
                         Repository r = (Repository) ctx.lookup("repo");

                         for (int i = 0; i < 20; i++) {
                                 JCRTest test = new JCRTest(r);
                                 test.execute();
                         }

                 } catch (Exception e){
                         e.printStackTrace();
                 }
         }

         public  class ThreadObserver extends Thread {
                 public void run() {
                         System.out.println("Observing thread started");
                         try {
                                 Session session = repository.login(new 
SimpleCredentials("", "".toCharArray()));
                                 EventListener el = new EventListener() {
                                         public void onEvent(EventIterator 
events) {
                                                 while (events.hasNext()) {
                                                         try {
                                         Thread.sleep(100);
                                     } catch (InterruptedException e) {}

                                     Event e = events.nextEvent();
                                                         if (e.getType() == 
Event.NODE_ADDED) {
                                                                 try {
                                                                         System.out.println("Node 
added : " + e.getPath());
                                                                 } catch 
(RepositoryException re) {
                                                                         re.printStackTrace();
                                                                 }
                                                         } 

                                                 }
                                         }
                                 };
                                 session.getWorkspace().getObservationManager().addEventListener(el, 
Event.NODE_ADDED, "/", true, null, null, false);
                                 session.save();
                                 //Some time to listen
                                 Thread.sleep(500);
                                 session.logout();
                         } catch (Exception e) {
                                 e.printStackTrace();
                         }
                 }
         }

         public  class ThreadDeleter extends Thread {
                 public void run() {
                         System.out.println("Deleter thread started");
                         try {
                                 Session session = repository.login(new 
SimpleCredentials("", "".toCharArray()));

                                 Node rn = session.getRootNode();
                                 for (NodeIterator ni = rn.getNodes(); 
ni.hasNext(); ) {
                                         Thread.sleep(100);
                                         Node currentNode = ni.nextNode();
                                         if 
(currentNode.getName().startsWith("node")) {
                                                 currentNode.remove();
                                         }
                                 }
                                 session.save();
                                 session.logout();

                         } catch (Exception e) {
                                 e.printStackTrace();
                         }
                 }
         }
}

Re: Problems with concurrent sessions

Posted by Nicolas Belisle <Ni...@bibl.ulaval.ca>.
Hi Oliver,

Thanks for your response.

 >Thereforeall your threads have to have their own session

My code doesn't share any session between threads. Only the repository is 
shared.

 > [...] or you have to synchronize session access to avoid
 >the exception you get (which is similar to ConcurrentModificationException
 > thrown by standard SDK collections/iterators).

Then a solution (as you suggest) would like something like this :

synchronized( repository ) {
         Session session = this.repository.login(new SimpleCredentials("", 
"".toCharArray()), null);
         //Do something
         session.save();
         session.logout();
}

What is the preferred approach ?

Also, if I'm responsible for preventing concurrrent sessions, then why is 
there those multithreading issues in JIRA : 
http://issues.apache.org/jira/browse/JCR-18 & 
http://issues.apache.org/jira/browse/JCR-155 ?

Regards,

Nicolas


Le 18:37 2005-07-06, vous avez écrit:
>Nicolas,
>
>the JCR spec does not require a session to be thread-safe so you are on
>your own to take care for problems like the one you describe. Therefore
>all your threads have to have their own session or you have to
>synchronize session access to avoid the exception you get (which is
>similar to ConcurrentModificationException thrown by standard SDK
>collections/iterators).
>
>Oliver
>
>
>Nicolas Belisle wrote:
>
> > Hi,
> >
> > I'm a Jackrabbit newbie. I'm doing some tests with the tool and I'm
> > having problem with concurrent sessions.
> >
> > I have joined my (simple) test class (JCRTest) and the exception I get
> > from running it.
> >
> > Anyone had similar problems ?
> >
> > Regards,
> >
> > Nicolas
> >
> >
> > -->The exception report:
> > java.util.NoSuchElementException: cdeab285-fdbc-4af3-918a-bf4316a29276
> > at
> > org.apache.jackrabbit.core.LazyItemIterator.next(LazyItemIterator.java:157)
> >
> > at
> > 
> org.apache.jackrabbit.core.LazyItemIterator.nextNode(LazyItemIterator.java:98)
> >
> > at app.JCRTest$ThreadDeleter.run(JCRTest.java:107)
> >
> > -->The class:
> > package app;
> >
> > import java.util.Hashtable;
> >
> > import javax.jcr.Node;
> > import javax.jcr.NodeIterator;
> > import javax.jcr.Repository;
> > import javax.jcr.RepositoryException;
> > import javax.jcr.Session;
> > import javax.jcr.SimpleCredentials;
> > import javax.jcr.observation.Event;
> > import javax.jcr.observation.EventIterator;
> > import javax.jcr.observation.EventListener;
> > import javax.naming.Context;
> > import javax.naming.InitialContext;
> >
> > import org.apache.jackrabbit.core.jndi.RegistryHelper;
> > import org.apache.jackrabbit.core.value.StringValue;
> >
> > public class JCRTest {
> >
> >         private Repository repository;
> >
> >         public JCRTest(Repository repository) {
> >                 this.repository = repository;
> >         }
> >
> >         public void execute() throws Exception {
> >                 Session session = this.repository.login(new
> > SimpleCredentials("", "".toCharArray()), null);
> >
> >                 Node rn = session.getRootNode();
> >                 new ThreadObserver().start();
> >                 Node n = rn.addNode("node" +
> > System.currentTimeMillis(), "nt:unstructured");
> >                 n.setProperty("testprop", new StringValue("Hello,
> > World."));
> >                 new ThreadDeleter().start();
> >
> >                 session.save();
> >                 session.logout();
> >         }
> >
> >         public static void main(String[] args) {
> >                 try {
> >                         String configFile = "repository/repository.xml";
> >                         String repHomeDir = "repository";
> >
> >                         Hashtable env = new Hashtable();
> >                         env.put(Context.INITIAL_CONTEXT_FACTORY,
> > "org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory");
> >                         env.put(Context.PROVIDER_URL, "localhost");
> >                         InitialContext ctx = new InitialContext(env);
> >
> >                         RegistryHelper.registerRepository(ctx, "repo",
> > configFile, repHomeDir, true);
> >                         Repository r = (Repository) ctx.lookup("repo");
> >
> >                         for (int i = 0; i < 20; i++) {
> >                                 JCRTest test = new JCRTest(r);
> >                                 test.execute();
> >                         }
> >
> >                 } catch (Exception e){
> >                         e.printStackTrace();
> >                 }
> >         }
> >
> >         public  class ThreadObserver extends Thread {
> >                 public void run() {
> >                         System.out.println("Observing thread started");
> >                         try {
> >                                 Session session = repository.login(new
> > SimpleCredentials("", "".toCharArray()));
> >                                 EventListener el = new EventListener() {
> >                                         public void
> > onEvent(EventIterator events) {
> >                                                 while
> > (events.hasNext()) {
> >                                                         try {
> >                                         Thread.sleep(100);
> >                                     } catch (InterruptedException e) {}
> >
> >                                     Event e = events.nextEvent();
> >                                                         if
> > (e.getType() == Event.NODE_ADDED) {
> >                                                                 try {
> >
> > System.out.println("Node added : " + e.getPath());
> >                                                                 }
> > catch (RepositoryException re) {
> >
> > re.printStackTrace();
> >                                                                 }
> >                                                         }
> >                                                 }
> >                                         }
> >                                 };
> >
> > session.getWorkspace().getObservationManager().addEventListener(el,
> > Event.NODE_ADDED, "/", true, null, null, false);
> >                                 session.save();
> >                                 //Some time to listen
> >                                 Thread.sleep(500);
> >                                 session.logout();
> >                         } catch (Exception e) {
> >                                 e.printStackTrace();
> >                         }
> >                 }
> >         }
> >
> >         public  class ThreadDeleter extends Thread {
> >                 public void run() {
> >                         System.out.println("Deleter thread started");
> >                         try {
> >                                 Session session = repository.login(new
> > SimpleCredentials("", "".toCharArray()));
> >
> >                                 Node rn = session.getRootNode();
> >                                 for (NodeIterator ni = rn.getNodes();
> > ni.hasNext(); ) {
> >                                         Thread.sleep(100);
> >                                         Node currentNode = ni.nextNode();
> >                                         if
> > (currentNode.getName().startsWith("node")) {
> >                                                 currentNode.remove();
> >                                         }
> >                                 }
> >                                 session.save();
> >                                 session.logout();
> >
> >                         } catch (Exception e) {
> >                                 e.printStackTrace();
> >                         }
> >                 }
> >         }
> > }
> >
>
>
>--
>Oliver Rossmueller
>Software Engineer and IT-Consultant
>Hamburg, Germany
>http://www.rossmueller.com


Re: Problems with concurrent sessions

Posted by Oliver Rossmueller <ol...@tuxerra.com>.
Nicolas,

the JCR spec does not require a session to be thread-safe so you are on
your own to take care for problems like the one you describe. Therefore
all your threads have to have their own session or you have to
synchronize session access to avoid the exception you get (which is
similar to ConcurrentModificationException thrown by standard SDK
collections/iterators).

Oliver


Nicolas Belisle wrote:

> Hi,
>
> I'm a Jackrabbit newbie. I'm doing some tests with the tool and I'm
> having problem with concurrent sessions.
>
> I have joined my (simple) test class (JCRTest) and the exception I get
> from running it.
>
> Anyone had similar problems ?
>
> Regards,
>
> Nicolas
>
>
> -->The exception report:
> java.util.NoSuchElementException: cdeab285-fdbc-4af3-918a-bf4316a29276
> at
> org.apache.jackrabbit.core.LazyItemIterator.next(LazyItemIterator.java:157)
>
> at
> org.apache.jackrabbit.core.LazyItemIterator.nextNode(LazyItemIterator.java:98)
>
> at app.JCRTest$ThreadDeleter.run(JCRTest.java:107)
>
> -->The class:
> package app;
>
> import java.util.Hashtable;
>
> import javax.jcr.Node;
> import javax.jcr.NodeIterator;
> import javax.jcr.Repository;
> import javax.jcr.RepositoryException;
> import javax.jcr.Session;
> import javax.jcr.SimpleCredentials;
> import javax.jcr.observation.Event;
> import javax.jcr.observation.EventIterator;
> import javax.jcr.observation.EventListener;
> import javax.naming.Context;
> import javax.naming.InitialContext;
>
> import org.apache.jackrabbit.core.jndi.RegistryHelper;
> import org.apache.jackrabbit.core.value.StringValue;
>
> public class JCRTest {
>
>         private Repository repository;
>
>         public JCRTest(Repository repository) {
>                 this.repository = repository;
>         }
>
>         public void execute() throws Exception {
>                 Session session = this.repository.login(new
> SimpleCredentials("", "".toCharArray()), null);
>
>                 Node rn = session.getRootNode();
>                 new ThreadObserver().start();
>                 Node n = rn.addNode("node" +
> System.currentTimeMillis(), "nt:unstructured");
>                 n.setProperty("testprop", new StringValue("Hello,
> World."));
>                 new ThreadDeleter().start();
>
>                 session.save();
>                 session.logout();
>         }
>
>         public static void main(String[] args) {
>                 try {
>                         String configFile = "repository/repository.xml";
>                         String repHomeDir = "repository";
>
>                         Hashtable env = new Hashtable();
>                         env.put(Context.INITIAL_CONTEXT_FACTORY,
> "org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory");
>                         env.put(Context.PROVIDER_URL, "localhost");
>                         InitialContext ctx = new InitialContext(env);
>
>                         RegistryHelper.registerRepository(ctx, "repo",
> configFile, repHomeDir, true);
>                         Repository r = (Repository) ctx.lookup("repo");
>
>                         for (int i = 0; i < 20; i++) {
>                                 JCRTest test = new JCRTest(r);
>                                 test.execute();
>                         }
>
>                 } catch (Exception e){
>                         e.printStackTrace();
>                 }
>         }
>
>         public  class ThreadObserver extends Thread {
>                 public void run() {
>                         System.out.println("Observing thread started");
>                         try {
>                                 Session session = repository.login(new
> SimpleCredentials("", "".toCharArray()));
>                                 EventListener el = new EventListener() {
>                                         public void
> onEvent(EventIterator events) {
>                                                 while
> (events.hasNext()) {
>                                                         try {
>                                         Thread.sleep(100);
>                                     } catch (InterruptedException e) {}
>
>                                     Event e = events.nextEvent();
>                                                         if
> (e.getType() == Event.NODE_ADDED) {
>                                                                 try {
>                                                                        
> System.out.println("Node added : " + e.getPath());
>                                                                 }
> catch (RepositoryException re) {
>                                                                        
> re.printStackTrace();
>                                                                 }
>                                                         }
>                                                 }
>                                         }
>                                 };
>                                
> session.getWorkspace().getObservationManager().addEventListener(el,
> Event.NODE_ADDED, "/", true, null, null, false);
>                                 session.save();
>                                 //Some time to listen
>                                 Thread.sleep(500);
>                                 session.logout();
>                         } catch (Exception e) {
>                                 e.printStackTrace();
>                         }
>                 }
>         }
>
>         public  class ThreadDeleter extends Thread {
>                 public void run() {
>                         System.out.println("Deleter thread started");
>                         try {
>                                 Session session = repository.login(new
> SimpleCredentials("", "".toCharArray()));
>
>                                 Node rn = session.getRootNode();
>                                 for (NodeIterator ni = rn.getNodes();
> ni.hasNext(); ) {
>                                         Thread.sleep(100);
>                                         Node currentNode = ni.nextNode();
>                                         if
> (currentNode.getName().startsWith("node")) {
>                                                 currentNode.remove();
>                                         }
>                                 }
>                                 session.save();
>                                 session.logout();
>
>                         } catch (Exception e) {
>                                 e.printStackTrace();
>                         }
>                 }
>         }
> }
>


-- 
Oliver Rossmueller
Software Engineer and IT-Consultant
Hamburg, Germany
http://www.rossmueller.com


Re: Problems with concurrent sessions

Posted by Julien Viet <ju...@jboss.org>.
the spec does not define isolation between concurrent sessions, you can 
read chapter "Reflecting Item State" 7.1.2

it is left up to the vendor, so the question is rather : how is 
isolation implemented in jackrabbit ?

it would be good to have clarifications indeed about how isolation is 
achieved is jackrabbit and if it is configurable
somewhere.

Nicolas Belisle wrote:

> Hi,
>
> Thanks for your response.
>
> Before posting a jira issue, I would like to make a few things clear.
>
> What is the session isolation level [ref. 
> http://www.unix.org.ua/orelly/java-ent/ebeans/ch08_03.htm] by default 
> ? Is it configurable ? At least, Jackrabbit seems to prevent dirty 
> reads (determined from tests). Is there any documentation regarding 
> isolation level ? I think it is a very important topic...
>
> Example :
> A : open session
> A : read nodes "test" & "test2"
> B : open session
> A : delete nodes "test" & "test2"
> B : save session //WHAT SHOULD HAPPEN ??
> B : logout
> A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
> A : logout
>
> The answers differs depending on the isolation level.
>
> Also, does the test cases cover concurrent sessions ?
>
> Regards,
>
> Nicolas
>
>
> Le 05:33 2005-07-07, vous avez écrit:
>
>> hi nicolas
>>
>> On 7/6/05, Nicolas Belisle <Ni...@bibl.ulaval.ca> wrote:
>> > Hi,
>> >
>> > I'm a Jackrabbit newbie. I'm doing some tests with the tool and I'm 
>> having
>> > problem with concurrent sessions.
>> >
>> > I have joined my (simple) test class (JCRTest) and the exception I 
>> get from
>> > running it.
>> >
>> > Anyone had similar problems ?
>>
>> this seems to be a bug, please post a jira issue.
>>
>> thanks
>> stefan
>>
>> >
>> > Regards,
>> >
>> > Nicolas
>> >
>> >
>> > -->The exception report:
>> > java.util.NoSuchElementException: cdeab285-fdbc-4af3-918a-bf4316a29276
>> > at 
>> org.apache.jackrabbit.core.LazyItemIterator.next(LazyItemIterator.java:157) 
>>
>> > at
>> > 
>> org.apache.jackrabbit.core.LazyItemIterator.nextNode(LazyItemIterator.java:98) 
>>
>> > at app.JCRTest$ThreadDeleter.run(JCRTest.java:107)
>> >
>> > -->The class:
>> > package app;
>> >
>> > import java.util.Hashtable;
>> >
>> > import javax.jcr.Node;
>> > import javax.jcr.NodeIterator;
>> > import javax.jcr.Repository;
>> > import javax.jcr.RepositoryException;
>> > import javax.jcr.Session;
>> > import javax.jcr.SimpleCredentials;
>> > import javax.jcr.observation.Event;
>> > import javax.jcr.observation.EventIterator;
>> > import javax.jcr.observation.EventListener;
>> > import javax.naming.Context;
>> > import javax.naming.InitialContext;
>> >
>> > import org.apache.jackrabbit.core.jndi.RegistryHelper;
>> > import org.apache.jackrabbit.core.value.StringValue;
>> >
>> > public class JCRTest {
>> >
>> >          private Repository repository;
>> >
>> >          public JCRTest(Repository repository) {
>> >                  this.repository = repository;
>> >          }
>> >
>> >          public void execute() throws Exception {
>> >                  Session session = this.repository.login(new
>> > SimpleCredentials("", "".toCharArray()), null);
>> >
>> >                  Node rn = session.getRootNode();
>> >                  new ThreadObserver().start();
>> >                  Node n = rn.addNode("node" + 
>> System.currentTimeMillis(),
>> > "nt:unstructured");
>> >                  n.setProperty("testprop", new StringValue("Hello, 
>> World."));
>> >                  new ThreadDeleter().start();
>> >
>> >                  session.save();
>> >                  session.logout();
>> >          }
>> >
>> >          public static void main(String[] args) {
>> >                  try {
>> >                          String configFile = 
>> "repository/repository.xml";
>> >                          String repHomeDir = "repository";
>> >
>> >                          Hashtable env = new Hashtable();
>> >                          env.put(Context.INITIAL_CONTEXT_FACTORY,
>> > 
>> "org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory");
>> >                          env.put(Context.PROVIDER_URL, "localhost");
>> >                          InitialContext ctx = new InitialContext(env);
>> >
>> >                          RegistryHelper.registerRepository(ctx, 
>> "repo",
>> > configFile, repHomeDir, true);
>> >                          Repository r = (Repository) 
>> ctx.lookup("repo");
>> >
>> >                          for (int i = 0; i < 20; i++) {
>> >                                  JCRTest test = new JCRTest(r);
>> >                                  test.execute();
>> >                          }
>> >
>> >                  } catch (Exception e){
>> >                          e.printStackTrace();
>> >                  }
>> >          }
>> >
>> >          public  class ThreadObserver extends Thread {
>> >                  public void run() {
>> >                          System.out.println("Observing thread 
>> started");
>> >                          try {
>> >                                  Session session = 
>> repository.login(new
>> > SimpleCredentials("", "".toCharArray()));
>> >                                  EventListener el = new 
>> EventListener() {
>> >                                          public void 
>> onEvent(EventIterator
>> > events) {
>> >                                                  while 
>> (events.hasNext()) {
>> >                                                          try {
>> >                                          Thread.sleep(100);
>> >                                      } catch (InterruptedException 
>> e) {}
>> >
>> >                                      Event e = events.nextEvent();
>> >                                                          if 
>> (e.getType() ==
>> > Event.NODE_ADDED) {
>> >                                                                  try {
>> >   System.out.println("Node
>> > added : " + e.getPath());
>> >                                                                  } 
>> catch
>> > (RepositoryException re) {
>> >   re.printStackTrace();
>> >                                                                  }
>> >                                                          }
>> >
>> >                                                  }
>> >                                          }
>> >                                  };
>> > session.getWorkspace().getObservationManager().addEventListener(el,
>> > Event.NODE_ADDED, "/", true, null, null, false);
>> >                                  session.save();
>> >                                  //Some time to listen
>> >                                  Thread.sleep(500);
>> >                                  session.logout();
>> >                          } catch (Exception e) {
>> >                                  e.printStackTrace();
>> >                          }
>> >                  }
>> >          }
>> >
>> >          public  class ThreadDeleter extends Thread {
>> >                  public void run() {
>> >                          System.out.println("Deleter thread started");
>> >                          try {
>> >                                  Session session = 
>> repository.login(new
>> > SimpleCredentials("", "".toCharArray()));
>> >
>> >                                  Node rn = session.getRootNode();
>> >                                  for (NodeIterator ni = rn.getNodes();
>> > ni.hasNext(); ) {
>> >                                          Thread.sleep(100);
>> >                                          Node currentNode = 
>> ni.nextNode();
>> >                                          if
>> > (currentNode.getName().startsWith("node")) {
>> >                                                  currentNode.remove();
>> >                                          }
>> >                                  }
>> >                                  session.save();
>> >                                  session.logout();
>> >
>> >                          } catch (Exception e) {
>> >                                  e.printStackTrace();
>> >                          }
>> >                  }
>> >          }
>> > }
>> >
>> >
>
>
>


-- 
Julien Viet
JBoss Portal Lead Developer


Re: Problems with concurrent sessions

Posted by Robert Ritchy <ro...@jarbo.com>.
Thanks - I did create the beginning of an examples page based old email 
exchanges: http://wiki.apache.org/jackrabbit/ExamplesPage
If anyone out there has something to add; please feel free to contribute 
your own example. In general, try to avoid examples explicitly covered 
in the spec or the main Jackrabbit site.

-Robert

Roy T. Fielding wrote:

> On Jul 8, 2005, at 9:33 AM, Robert Ritchy wrote:
>
>> Summary information like this would be perfect for your wiki 
>> (http://wiki.apache.org/jackrabbit). Are non-commiters encouraged to 
>> sign up and contribute to the wiki?  If so, I'd also like to start a 
>> Best Practice (or maybe just 'Examples') Page.
>
>
> Yes, that is the only reason we have a wiki -- committers could
> just commit it to the docs.  Go for it.
>
> ....Roy
>
>

Re: Problems with concurrent sessions

Posted by "Roy T. Fielding" <fi...@gbiv.com>.
On Jul 8, 2005, at 9:33 AM, Robert Ritchy wrote:

> Summary information like this would be perfect for your wiki 
> (http://wiki.apache.org/jackrabbit). Are non-commiters encouraged to 
> sign up and contribute to the wiki?  If so, I'd also like to start a 
> Best Practice (or maybe just 'Examples') Page.

Yes, that is the only reason we have a wiki -- committers could
just commit it to the docs.  Go for it.

....Roy


Re: Problems with concurrent sessions

Posted by Robert Ritchy <ro...@jarbo.com>.
Stefan Guggisberg wrote:

>
>
>dominique roughly sketched the 3 layers (transient, local, shared) 
>involved in jackrabbit's implementation of the 'read committed' isolation level 
>in an earlier thread:
>http://article.gmane.org/gmane.comp.apache.jackrabbit.devel/1223
>
>cheers
>stefan
>
>  
>

Summary information like this would be perfect for your wiki 
(http://wiki.apache.org/jackrabbit). Are non-commiters encouraged to 
sign up and contribute to the wiki?  If so, I'd also like to start a 
Best Practice (or maybe just 'Examples') Page.

-robert



Re: Problems with concurrent sessions

Posted by Stefan Guggisberg <st...@gmail.com>.
On 7/8/05, Marcel Reutegger <ma...@gmx.net> wrote:
> Hi Nicolas,
> 
> Nicolas Belisle wrote:
> > What is the session isolation level [ref.
> > http://www.unix.org.ua/orelly/java-ent/ebeans/ch08_03.htm] by default ?
> 
> the default is read commited.
> 
> > Is it configurable ?
> 
> no, but you may use locks to further control isolation level.
> 
> a session in jackrabbit will see changes as soon as they are committed.
> you may however use locks to achieve a higher isolation level.
> successfully setting a lock on a node will give you repeatable read for
> that subtree. if you do that on the root node you get serializable
> isolation level.
> 
> > At least, Jackrabbit seems to prevent dirty reads
> > (determined from tests). Is there any documentation regarding isolation
> > level ? I think it is a very important topic...
> 
> Unfortunately there is currently no documentation on isolation levels,
> but this thread may be a good starting point to collect all relevant
> information and then put it into a nice xdoc or wiki page.

dominique roughly sketched the 3 layers (transient, local, shared) 
involved in jackrabbit's implementation of the 'read committed' isolation level 
in an earlier thread:
http://article.gmane.org/gmane.comp.apache.jackrabbit.devel/1223

cheers
stefan

> 
> > Example :
> > A : open session
> > A : read nodes "test" & "test2"
> > B : open session
> > A : delete nodes "test" & "test2"
> > B : save session //WHAT SHOULD HAPPEN ??
> > B : logout
> > A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
> > A : logout
> 
> I cannot quite follow you here. thread B seems to save a session without
> having made changes to it? that means 'B : save session' is basically no
> op. But I guess that's not what you want to know...
> 
> regards
>   marcel
>

Re: Problems with concurrent sessions

Posted by Marcel Reutegger <ma...@gmx.net>.
Hi Nicolas,

Nicolas Belisle wrote:
> Hi Marcel,
> 
> If all three tryLock() fail, the event is certainly active. The event is 
> then waiting for an ACTIVE lock to be released (the last tryLock() 
> confirms that fact). The event is not cancelled/removed until the lock 
> is obtained. Then doInTransaction() is called.
> 
> The only way I see to stop the transaction from executing is to stop the 
> server...
> 
> Am I missing something ?

you are right. the listener will still be active when a tryLock() in 
onEvent() fails. and will then be called again on the next unlock event.

sorry about the confusion I may have caused...

regards
  marcel

Re: Problems with concurrent sessions

Posted by Nicolas Belisle <Ni...@bibl.ulaval.ca>.
Hi Marcel,

If all three tryLock() fail, the event is certainly active. The event is 
then waiting for an ACTIVE lock to be released (the last tryLock() confirms 
that fact). The event is not cancelled/removed until the lock is obtained. 
Then doInTransaction() is called.

The only way I see to stop the transaction from executing is to stop the 
server...

Am I missing something ?

Regards,

Nicolas


Le 05:54 2005-07-19, vous avez écrit:
>Hi Nicolas,
>
>Looks better now, but there are still cases where the doTransaction() 
>method will not be called, though very unlikely: when all three tryLock() 
>attempts fail. not very likely but theoretically possible...
>
>regards
>  marcel
>
>Nicolas Belisle wrote:
>>Hi,
>>Thanks again for your comments.
>>Here's the second version of my template class. It should resolves the 
>>concurrency issues you mentionned :
>>package app;
>>import javax.jcr.Credentials;
>>import javax.jcr.LoginException;
>>import javax.jcr.Node;
>>import javax.jcr.Repository;
>>import javax.jcr.RepositoryException;
>>import javax.jcr.Session;
>>import javax.jcr.lock.LockException;
>>import javax.jcr.observation.Event;
>>import javax.jcr.observation.EventIterator;
>>import javax.jcr.observation.EventListener;
>>public abstract class SerializableTemplate {
>>     private Session session;
>>     private Node scope;
>>     private boolean done = false;
>>     private EventListener el;
>>     public SerializableTemplate(Repository repository, Credentials cr, 
>> String scopePath) throws LoginException, RepositoryException {
>>         session = repository.login(cr);
>>         scope = session.getRootNode().getNode(scopePath);
>>         //scope = session.getNodeByUUID(scope.getUUID());
>>     }
>>     public abstract void doInTransaction(Session session) throws 
>> RepositoryException;
>>     public void execute() throws RepositoryException {
>>         if (tryLock()) {
>>             return;
>>         }
>>         this.el = new EventListener() {
>>             public void onEvent(EventIterator events) {
>>                 try {
>>                     tryLock();
>>                 } catch (RepositoryException e) {
>>                     throw new RuntimeException(e);
>>                 }
>>             }
>>         };
>>
>>session.getWorkspace().getObservationManager().addEventListener(el, 
>>Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);
>>         //Try again, in case the lock is removed before observer could 
>> be put in place
>>         tryLock();
>>     }
>>     private synchronized boolean tryLock() throws RepositoryException {
>>         try {
>>             if (done) {
>>                 return false;
>>             }
>>             if (!scope.isLocked()) {
>>                 scope.lock(true, true);
>>                 try {
>>                     if (el != null) {
>>
>>session.getWorkspace().getObservationManager().removeEventListener(el);
>>                     }
>>                     doInTransaction(session);
>>                 } finally {
>>                     done = true;
>>                     if (session.isLive()) {
>>                         session.logout();
>>                     }
>>                 }
>>                 return true;
>>             }
>>         } catch (LockException e) {
>>             e.printStackTrace();
>>         }
>>         return false;
>>     }
>>}
>>Here's how to use it :
>>SerializableTemplate sTemplate = new SerializableTemplate(repository, new 
>>SimpleCredentials("user", "password".toCharArray()), "node/path") {
>>         //@Override
>>         public void doInTransaction(Session session) throws 
>> RepositoryException {
>>                 //Do your favorite transaction...
>>         };
>>sTemplate.execute();
>>
>>For the constructor you suggested, I actually came up with a similiar 
>>design at first, but found a problem with it : since the template class 
>>might use an EventListener the class should be responsible for closing 
>>the session (the EventListener can wait a while...). Else, the event 
>>could be removed by the user before being invoked. That's the reason for 
>>my ugly constructor.
>>I welcome your comments again...
>>Regards,
>>Nicolas


Re: Problems with concurrent sessions

Posted by Marcel Reutegger <ma...@gmx.net>.
Hi Nicolas,

Looks better now, but there are still cases where the doTransaction() 
method will not be called, though very unlikely: when all three 
tryLock() attempts fail. not very likely but theoretically possible...

regards
  marcel

Nicolas Belisle wrote:
> Hi,
> 
> Thanks again for your comments.
> 
> Here's the second version of my template class. It should resolves the 
> concurrency issues you mentionned :
> 
> package app;
> 
> import javax.jcr.Credentials;
> import javax.jcr.LoginException;
> import javax.jcr.Node;
> import javax.jcr.Repository;
> import javax.jcr.RepositoryException;
> import javax.jcr.Session;
> import javax.jcr.lock.LockException;
> import javax.jcr.observation.Event;
> import javax.jcr.observation.EventIterator;
> import javax.jcr.observation.EventListener;
> 
> public abstract class SerializableTemplate {
> 
>     private Session session;
>     private Node scope;
>     private boolean done = false;
>     private EventListener el;
> 
>     public SerializableTemplate(Repository repository, Credentials cr, 
> String scopePath) throws LoginException, RepositoryException {
>         session = repository.login(cr);
>         scope = session.getRootNode().getNode(scopePath);
>         //scope = session.getNodeByUUID(scope.getUUID());
>     }
> 
>     public abstract void doInTransaction(Session session) throws 
> RepositoryException;
> 
>     public void execute() throws RepositoryException {
>         if (tryLock()) {
>             return;
>         }
> 
>         this.el = new EventListener() {
>             public void onEvent(EventIterator events) {
>                 try {
>                     tryLock();
>                 } catch (RepositoryException e) {
>                     throw new RuntimeException(e);
>                 }
>             }
>         };
>         
> session.getWorkspace().getObservationManager().addEventListener(el, 
> Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);
> 
>         //Try again, in case the lock is removed before observer could 
> be put in place
>         tryLock();
>     }
> 
>     private synchronized boolean tryLock() throws RepositoryException {
>         try {
>             if (done) {
>                 return false;
>             }
> 
>             if (!scope.isLocked()) {
>                 scope.lock(true, true);
>                 try {
>                     if (el != null) {
>                         
> session.getWorkspace().getObservationManager().removeEventListener(el);
>                     }
>                     doInTransaction(session);
>                 } finally {
>                     done = true;
>                     if (session.isLive()) {
>                         session.logout();
>                     }
>                 }
>                 return true;
>             }
>         } catch (LockException e) {
>             e.printStackTrace();
>         }
>         return false;
>     }
> }
> 
> Here's how to use it :
> 
> SerializableTemplate sTemplate = new SerializableTemplate(repository, 
> new SimpleCredentials("user", "password".toCharArray()), "node/path") {
>         //@Override
>         public void doInTransaction(Session session) throws 
> RepositoryException {
>                 //Do your favorite transaction...
>         };
> sTemplate.execute();
> 
> 
> For the constructor you suggested, I actually came up with a similiar 
> design at first, but found a problem with it : since the template class 
> might use an EventListener the class should be responsible for closing 
> the session (the EventListener can wait a while...). Else, the event 
> could be removed by the user before being invoked. That's the reason for 
> my ugly constructor.
> 
> I welcome your comments again...
> 
> Regards,
> 
> Nicolas
> 

Re: Problems with concurrent sessions

Posted by Nicolas Belisle <Ni...@bibl.ulaval.ca>.
Hi,

Thanks again for your comments.

Here's the second version of my template class. It should resolves the 
concurrency issues you mentionned :

package app;

import javax.jcr.Credentials;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.lock.LockException;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;

public abstract class SerializableTemplate {

     private Session session;
     private Node scope;
     private boolean done = false;
     private EventListener el;

     public SerializableTemplate(Repository repository, Credentials cr, 
String scopePath) throws LoginException, RepositoryException {
         session = repository.login(cr);
         scope = session.getRootNode().getNode(scopePath);
         //scope = session.getNodeByUUID(scope.getUUID());
     }

     public abstract void doInTransaction(Session session) throws 
RepositoryException;

     public void execute() throws RepositoryException {
         if (tryLock()) {
             return;
         }

         this.el = new EventListener() {
             public void onEvent(EventIterator events) {
                 try {
                     tryLock();
                 } catch (RepositoryException e) {
                     throw new RuntimeException(e);
                 }
             }
         };
         session.getWorkspace().getObservationManager().addEventListener(el, 
Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);

         //Try again, in case the lock is removed before observer could be 
put in place
         tryLock();
     }

     private synchronized boolean tryLock() throws RepositoryException {
         try {
             if (done) {
                 return false;
             }

             if (!scope.isLocked()) {
                 scope.lock(true, true);
                 try {
                     if (el != null) {
                         session.getWorkspace().getObservationManager().removeEventListener(el);
                     }
                     doInTransaction(session);
                 } finally {
                     done = true;
                     if (session.isLive()) {
                         session.logout();
                     }
                 }
                 return true;
             }
         } catch (LockException e) {
             e.printStackTrace();
         }
         return false;
     }
}

Here's how to use it :

SerializableTemplate sTemplate = new SerializableTemplate(repository, new 
SimpleCredentials("user", "password".toCharArray()), "node/path") {
         //@Override
         public void doInTransaction(Session session) throws 
RepositoryException {
                 //Do your favorite transaction...
         };
sTemplate.execute();


For the constructor you suggested, I actually came up with a similiar 
design at first, but found a problem with it : since the template class 
might use an EventListener the class should be responsible for closing the 
session (the EventListener can wait a while...). Else, the event could be 
removed by the user before being invoked. That's the reason for my ugly 
constructor.

I welcome your comments again...

Regards,

Nicolas


Le 05:07 2005-07-15, vous avez écrit:
>Hi Nicolas,
>
>I can see where you are heading for ;)
>
>I think the constructor take the node that acts as the lock scope. It 
>should provide all that is needed:
>
>public SerializableTemplate(Node scope) {
>         session = scope.getSession();
>         this.scope = scope;
>}
>
>you may then also ommit the session parameter in doInTransaction. And just 
>as a minor improvement the method should be allowed to throw a 
>RepositoryException.
>
>After checking whether a node is locked it is not guaranteed that you can 
>then lock the node.
>A similar concurrency problem can arise when isLocked() returns true, 
>between that call and the listener registration the node might get 
>unlocked. so, you don't get an event for that and keep waiting.
>
>regards
>  marcel
>
>Nicolas Belisle wrote:
>>I just thought about something like this (Note that I've only done a few 
>>tests on that class.)  :
>>public abstract class SerializableTemplate {
>>     private Session session;
>>     private Node scope;
>>     public SerializableTemplate(Repository repository, Credentials cr, 
>> String scopePath) throws LoginException, RepositoryException {
>>         session = repository.login(cr);
>>         scope = session.getRootNode().getNode(scopePath);
>>     }
>>     public abstract void doInTransaction(Session session);
>>     public void execute() {
>>         try {
>>             if (!scope.isLocked()) {
>>                 scope.lock(true, true);
>>                 doInTransaction(session);
>>                 if (session.isLive()) {
>>                     session.logout();
>>                 }
>>             } else {
>>                 EventListener el = new EventListener() {
>>                     public void onEvent(EventIterator events) {
>>                         try {
>>                             if (!scope.isLocked()) {
>>                                 scope.lock(true, true);
>>                                 doInTransaction(session);
>>                                 if (session.isLive()) {
>>                                     session.logout();
>>                                 }
>>                             }
>>                         } catch (Exception e) {
>>                             throw new RuntimeException(e);
>>                         }
>>                     }
>>                 };
>>
>>session.getWorkspace().getObservationManager().addEventListener(el, 
>>Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);
>>             }
>>         } catch (Exception e) {
>>             throw new RuntimeException(e);
>>         }
>>     }
>>}
>>I don't like a few things about that class, especially the constructor...
>>What do you think overall ?
>>  > Again I think this always depends on the application on top of the 
>> repository.
>>  > Setting an isolation level as a global property does not seems to be 
>> a good idea to me.
>>Well, if many applications need control of their isolation level, maybe 
>>that feature should be implemented in one place or documented in a worked 
>>example...
>>Regards,
>>Nicolas


Re: Problems with concurrent sessions

Posted by Marcel Reutegger <ma...@gmx.net>.
Hi Nicolas,

I can see where you are heading for ;)

I think the constructor take the node that acts as the lock scope. It 
should provide all that is needed:

public SerializableTemplate(Node scope) {
         session = scope.getSession();
         this.scope = scope;
}

you may then also ommit the session parameter in doInTransaction. And 
just as a minor improvement the method should be allowed to throw a 
RepositoryException.

After checking whether a node is locked it is not guaranteed that you 
can then lock the node.
A similar concurrency problem can arise when isLocked() returns true, 
between that call and the listener registration the node might get 
unlocked. so, you don't get an event for that and keep waiting.

regards
  marcel

Nicolas Belisle wrote:
> I just thought about something like this (Note that I've only done a few 
> tests on that class.)  :
> 
> public abstract class SerializableTemplate {
> 
>     private Session session;
>     private Node scope;
> 
>     public SerializableTemplate(Repository repository, Credentials cr, 
> String scopePath) throws LoginException, RepositoryException {
>         session = repository.login(cr);
>         scope = session.getRootNode().getNode(scopePath);
>     }
> 
>     public abstract void doInTransaction(Session session);
> 
>     public void execute() {
>         try {
>             if (!scope.isLocked()) {
>                 scope.lock(true, true);
>                 doInTransaction(session);
>                 if (session.isLive()) {
>                     session.logout();
>                 }
>             } else {
>                 EventListener el = new EventListener() {
>                     public void onEvent(EventIterator events) {
>                         try {
>                             if (!scope.isLocked()) {
>                                 scope.lock(true, true);
>                                 doInTransaction(session);
>                                 if (session.isLive()) {
>                                     session.logout();
>                                 }
>                             }
>                         } catch (Exception e) {
>                             throw new RuntimeException(e);
>                         }
>                     }
>                 };
>                 
> session.getWorkspace().getObservationManager().addEventListener(el, 
> Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);
>             }
>         } catch (Exception e) {
>             throw new RuntimeException(e);
>         }
>     }
> }
> 
> I don't like a few things about that class, especially the constructor...
> 
> What do you think overall ?
> 
>  > Again I think this always depends on the application on top of the 
> repository.
>  > Setting an isolation level as a global property does not seems to be 
> a good idea to me.
> 
> Well, if many applications need control of their isolation level, maybe 
> that feature should be implemented in one place or documented in a 
> worked example...
> 
> Regards,
> 
> Nicolas
> 

Re: Problems with concurrent sessions

Posted by Nicolas Belisle <Ni...@bibl.ulaval.ca>.
Hi again,

 > You probably want a lock call that is blocking and returns when the lock 
could be retrieved.
 > Locks in jsr-170 were primarily designed to lock nodes for a longer time 
and not just while
 > in a transaction. Maybe a blocking variant of the getLock() method is 
something we should
 > consider for the next jcr version.

I just thought about something like this (Note that I've only done a few 
tests on that class.)  :

import javax.jcr.Credentials;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;

public abstract class SerializableTemplate {

     private Session session;
     private Node scope;

     public SerializableTemplate(Repository repository, Credentials cr, 
String scopePath) throws LoginException, RepositoryException {
         session = repository.login(cr);
         scope = session.getRootNode().getNode(scopePath);
     }

     public abstract void doInTransaction(Session session);

     public void execute() {
         try {
             if (!scope.isLocked()) {
                 scope.lock(true, true);
                 doInTransaction(session);
                 if (session.isLive()) {
                     session.logout();
                 }
             } else {
                 EventListener el = new EventListener() {
                     public void onEvent(EventIterator events) {
                         try {
                             if (!scope.isLocked()) {
                                 scope.lock(true, true);
                                 doInTransaction(session);
                                 if (session.isLive()) {
                                     session.logout();
                                 }
                             }
                         } catch (Exception e) {
                             throw new RuntimeException(e);
                         }
                     }
                 };
                 session.getWorkspace().getObservationManager().addEventListener(el, 
Event.PROPERTY_REMOVED, scope.getPath(), true, null, null, false);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
}

I don't like a few things about that class, especially the constructor...

What do you think overall ?

 > Again I think this always depends on the application on top of the 
repository.
 > Setting an isolation level as a global property does not seems to be a 
good idea to me.

Well, if many applications need control of their isolation level, maybe 
that feature should be implemented in one place or documented in a worked 
example...

Regards,

Nicolas



Le 12:34 2005-07-08, vous avez écrit:
>Hi Nicolas,
>
>Nicolas Belisle wrote:
>>  > no, but you may use locks to further control isolation level.
>>There seems to be at least two problems with that approach :
>>- You will get a (unpleasant) javax.jcr.lock.LockException if another 
>>transaction tries to lock the node (or a child).
>
>You probably want a lock call that is blocking and returns when the lock 
>could be retrieved. Locks in jsr-170 were primarily designed to lock nodes 
>for a longer time and not just while in a transaction. Maybe a blocking 
>variant of the getLock() method is something we should consider for the 
>next jcr version.
>
>>- It is not enforced even though it should be used everywhere, even for 
>>only a read.
>
>This depends on the application. I don't think you have to lock the 
>workspace when your applicaton is fine with an read-committed isolation level.
>
>>Sorry there was an error in my example. I should make more sense this way :
>>Example v.2 :
>>A : open session
>>A : read nodes "test" & "test2"
>>B : open session
>>B : delete nodes "test" & "test2"
>>B : save session //WHAT SHOULD HAPPEN ??
>>B : logout
>>A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
>>A : logout
>
>yeah, that definitively makes more sense ;)
>
>deleting the two nodes with Session B will be successful, unless session A 
>has locked the two nodes.
>regarding the second attempt to read the two nodes with session A, this 
>depends whether A already obtained references to the nodes or not.
>
>If A already has reference to those two nodes, calles that read the state 
>of those nodes will throw an InvalidItemStateException. Indicating that 
>the nodes do not exist anymore.
>
>If A tries to retrieve the nodes again. E.g. by calling getNodes() on the 
>parent of the just deleted nodes. The returned NodeIterator will simply 
>not contain the nodes anymore.
>
>>It would be useful if the isolation level could configured at the 
>>repository level. So if your application needs better isolation or 
>>performance you would only need to change the configuration and not your code.
>
>Again I think this always depends on the application on top of the 
>repository. Setting an isolation level as a global property does not seems 
>to be a good idea to me.
>
>>An implementation could (hypothetically) use locks in SessionImpl and use 
>>events (when a session saves/logout) to prevent throwing exceptions. A 
>>queue would hold pending operations. However, we would have to be careful 
>>about long lived sessions...
>
>I'm sorry, I cannot follow you here...
>
>regards
>  marcel


Re: Problems with concurrent sessions

Posted by Marcel Reutegger <ma...@gmx.net>.
Hi Nicolas,

Nicolas Belisle wrote:
>  > no, but you may use locks to further control isolation level.
> 
> There seems to be at least two problems with that approach :
> 
> - You will get a (unpleasant) javax.jcr.lock.LockException if another 
> transaction tries to lock the node (or a child).

You probably want a lock call that is blocking and returns when the lock 
could be retrieved. Locks in jsr-170 were primarily designed to lock 
nodes for a longer time and not just while in a transaction. Maybe a 
blocking variant of the getLock() method is something we should consider 
for the next jcr version.

> - It is not enforced even though it should be used everywhere, even for 
> only a read.

This depends on the application. I don't think you have to lock the 
workspace when your applicaton is fine with an read-committed isolation 
level.

> Sorry there was an error in my example. I should make more sense this way :
> 
> Example v.2 :
> A : open session
> A : read nodes "test" & "test2"
> B : open session
> B : delete nodes "test" & "test2"
> B : save session //WHAT SHOULD HAPPEN ??
> B : logout
> A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
> A : logout

yeah, that definitively makes more sense ;)

deleting the two nodes with Session B will be successful, unless session 
A has locked the two nodes.
regarding the second attempt to read the two nodes with session A, this 
depends whether A already obtained references to the nodes or not.

If A already has reference to those two nodes, calles that read the 
state of those nodes will throw an InvalidItemStateException. Indicating 
that the nodes do not exist anymore.

If A tries to retrieve the nodes again. E.g. by calling getNodes() on 
the parent of the just deleted nodes. The returned NodeIterator will 
simply not contain the nodes anymore.

> It would be useful if the isolation level could configured at the 
> repository level. So if your application needs better isolation or 
> performance you would only need to change the configuration and not your 
> code.

Again I think this always depends on the application on top of the 
repository. Setting an isolation level as a global property does not 
seems to be a good idea to me.

> An implementation could (hypothetically) use locks in SessionImpl and 
> use events (when a session saves/logout) to prevent throwing exceptions. 
> A queue would hold pending operations. However, we would have to be 
> careful about long lived sessions...

I'm sorry, I cannot follow you here...

regards
  marcel

Re: Problems with concurrent sessions

Posted by Nicolas Belisle <Ni...@bibl.ulaval.ca>.
Hi,

Many thanks for your response.

 > no, but you may use locks to further control isolation level.

There seems to be at least two problems with that approach :

- You will get a (unpleasant) javax.jcr.lock.LockException if another 
transaction tries to lock the node (or a child).
- It is not enforced even though it should be used everywhere, even for 
only a read.

 > I cannot quite follow you here. thread B seems to save a session without 
having
 > made  changes to it? that means 'B : save session' is basically no op. 
But I guess
 > that's not  what you want to know...

Sorry there was an error in my example. I should make more sense this way :

Example v.2 :
A : open session
A : read nodes "test" & "test2"
B : open session
B : delete nodes "test" & "test2"
B : save session //WHAT SHOULD HAPPEN ??
B : logout
A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
A : logout

It would be useful if the isolation level could configured at the 
repository level. So if your application needs better isolation or 
performance you would only need to change the configuration and not your code.
An implementation could (hypothetically) use locks in SessionImpl and use 
events (when a session saves/logout) to prevent throwing exceptions. A 
queue would hold pending operations. However, we would have to be careful 
about long lived sessions...

What do you think ?

Regards,

Nicolas


Le 04:06 2005-07-08, vous avez écrit:
>Hi Nicolas,
>
>Nicolas Belisle wrote:
>>What is the session isolation level [ref. 
>>http://www.unix.org.ua/orelly/java-ent/ebeans/ch08_03.htm] by default ?
>
>the default is read commited.
>
>>Is it configurable ?
>
>no, but you may use locks to further control isolation level.
>
>a session in jackrabbit will see changes as soon as they are committed. 
>you may however use locks to achieve a higher isolation level. 
>successfully setting a lock on a node will give you repeatable read for 
>that subtree. if you do that on the root node you get serializable 
>isolation level.
>
>>At least, Jackrabbit seems to prevent dirty reads (determined from 
>>tests). Is there any documentation regarding isolation level ? I think it 
>>is a very important topic...
>
>Unfortunately there is currently no documentation on isolation levels, but 
>this thread may be a good starting point to collect all relevant 
>information and then put it into a nice xdoc or wiki page.
>
>>Example :
>>A : open session
>>A : read nodes "test" & "test2"
>>B : open session
>>A : delete nodes "test" & "test2"
>>B : save session //WHAT SHOULD HAPPEN ??
>>B : logout
>>A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
>>A : logout
>
>I cannot quite follow you here. thread B seems to save a session without 
>having made changes to it? that means 'B : save session' is basically no 
>op. But I guess that's not what you want to know...
>
>regards
>  marcel


Re: Problems with concurrent sessions

Posted by Marcel Reutegger <ma...@gmx.net>.
Hi Nicolas,

Nicolas Belisle wrote:
> What is the session isolation level [ref. 
> http://www.unix.org.ua/orelly/java-ent/ebeans/ch08_03.htm] by default ?

the default is read commited.

> Is it configurable ?

no, but you may use locks to further control isolation level.

a session in jackrabbit will see changes as soon as they are committed. 
you may however use locks to achieve a higher isolation level. 
successfully setting a lock on a node will give you repeatable read for 
that subtree. if you do that on the root node you get serializable 
isolation level.

> At least, Jackrabbit seems to prevent dirty reads 
> (determined from tests). Is there any documentation regarding isolation 
> level ? I think it is a very important topic...

Unfortunately there is currently no documentation on isolation levels, 
but this thread may be a good starting point to collect all relevant 
information and then put it into a nice xdoc or wiki page.

> Example :
> A : open session
> A : read nodes "test" & "test2"
> B : open session
> A : delete nodes "test" & "test2"
> B : save session //WHAT SHOULD HAPPEN ??
> B : logout
> A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
> A : logout

I cannot quite follow you here. thread B seems to save a session without 
having made changes to it? that means 'B : save session' is basically no 
op. But I guess that's not what you want to know...

regards
  marcel

Re: Problems with concurrent sessions

Posted by Nicolas Belisle <Ni...@bibl.ulaval.ca>.
Hi,

Thanks for your response.

Before posting a jira issue, I would like to make a few things clear.

What is the session isolation level [ref. 
http://www.unix.org.ua/orelly/java-ent/ebeans/ch08_03.htm] by default ? Is 
it configurable ? At least, Jackrabbit seems to prevent dirty reads 
(determined from tests). Is there any documentation regarding isolation 
level ? I think it is a very important topic...

Example :
A : open session
A : read nodes "test" & "test2"
B : open session
A : delete nodes "test" & "test2"
B : save session //WHAT SHOULD HAPPEN ??
B : logout
A : read nodes "test" & "test2" //WHAT SHOULD HAPPEN ??
A : logout

The answers differs depending on the isolation level.

Also, does the test cases cover concurrent sessions ?

Regards,

Nicolas


Le 05:33 2005-07-07, vous avez écrit:
>hi nicolas
>
>On 7/6/05, Nicolas Belisle <Ni...@bibl.ulaval.ca> wrote:
> > Hi,
> >
> > I'm a Jackrabbit newbie. I'm doing some tests with the tool and I'm having
> > problem with concurrent sessions.
> >
> > I have joined my (simple) test class (JCRTest) and the exception I get from
> > running it.
> >
> > Anyone had similar problems ?
>
>this seems to be a bug, please post a jira issue.
>
>thanks
>stefan
>
> >
> > Regards,
> >
> > Nicolas
> >
> >
> > -->The exception report:
> > java.util.NoSuchElementException: cdeab285-fdbc-4af3-918a-bf4316a29276
> > at 
> org.apache.jackrabbit.core.LazyItemIterator.next(LazyItemIterator.java:157)
> > at
> > 
> org.apache.jackrabbit.core.LazyItemIterator.nextNode(LazyItemIterator.java:98)
> > at app.JCRTest$ThreadDeleter.run(JCRTest.java:107)
> >
> > -->The class:
> > package app;
> >
> > import java.util.Hashtable;
> >
> > import javax.jcr.Node;
> > import javax.jcr.NodeIterator;
> > import javax.jcr.Repository;
> > import javax.jcr.RepositoryException;
> > import javax.jcr.Session;
> > import javax.jcr.SimpleCredentials;
> > import javax.jcr.observation.Event;
> > import javax.jcr.observation.EventIterator;
> > import javax.jcr.observation.EventListener;
> > import javax.naming.Context;
> > import javax.naming.InitialContext;
> >
> > import org.apache.jackrabbit.core.jndi.RegistryHelper;
> > import org.apache.jackrabbit.core.value.StringValue;
> >
> > public class JCRTest {
> >
> >          private Repository repository;
> >
> >          public JCRTest(Repository repository) {
> >                  this.repository = repository;
> >          }
> >
> >          public void execute() throws Exception {
> >                  Session session = this.repository.login(new
> > SimpleCredentials("", "".toCharArray()), null);
> >
> >                  Node rn = session.getRootNode();
> >                  new ThreadObserver().start();
> >                  Node n = rn.addNode("node" + System.currentTimeMillis(),
> > "nt:unstructured");
> >                  n.setProperty("testprop", new StringValue("Hello, 
> World."));
> >                  new ThreadDeleter().start();
> >
> >                  session.save();
> >                  session.logout();
> >          }
> >
> >          public static void main(String[] args) {
> >                  try {
> >                          String configFile = "repository/repository.xml";
> >                          String repHomeDir = "repository";
> >
> >                          Hashtable env = new Hashtable();
> >                          env.put(Context.INITIAL_CONTEXT_FACTORY,
> > "org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory");
> >                          env.put(Context.PROVIDER_URL, "localhost");
> >                          InitialContext ctx = new InitialContext(env);
> >
> >                          RegistryHelper.registerRepository(ctx, "repo",
> > configFile, repHomeDir, true);
> >                          Repository r = (Repository) ctx.lookup("repo");
> >
> >                          for (int i = 0; i < 20; i++) {
> >                                  JCRTest test = new JCRTest(r);
> >                                  test.execute();
> >                          }
> >
> >                  } catch (Exception e){
> >                          e.printStackTrace();
> >                  }
> >          }
> >
> >          public  class ThreadObserver extends Thread {
> >                  public void run() {
> >                          System.out.println("Observing thread started");
> >                          try {
> >                                  Session session = repository.login(new
> > SimpleCredentials("", "".toCharArray()));
> >                                  EventListener el = new EventListener() {
> >                                          public void onEvent(EventIterator
> > events) {
> >                                                  while (events.hasNext()) {
> >                                                          try {
> >                                          Thread.sleep(100);
> >                                      } catch (InterruptedException e) {}
> >
> >                                      Event e = events.nextEvent();
> >                                                          if (e.getType() ==
> > Event.NODE_ADDED) {
> >                                                                  try {
> > 
>   System.out.println("Node
> > added : " + e.getPath());
> >                                                                  } catch
> > (RepositoryException re) {
> > 
>   re.printStackTrace();
> >                                                                  }
> >                                                          }
> >
> >                                                  }
> >                                          }
> >                                  };
> > 
> session.getWorkspace().getObservationManager().addEventListener(el,
> > Event.NODE_ADDED, "/", true, null, null, false);
> >                                  session.save();
> >                                  //Some time to listen
> >                                  Thread.sleep(500);
> >                                  session.logout();
> >                          } catch (Exception e) {
> >                                  e.printStackTrace();
> >                          }
> >                  }
> >          }
> >
> >          public  class ThreadDeleter extends Thread {
> >                  public void run() {
> >                          System.out.println("Deleter thread started");
> >                          try {
> >                                  Session session = repository.login(new
> > SimpleCredentials("", "".toCharArray()));
> >
> >                                  Node rn = session.getRootNode();
> >                                  for (NodeIterator ni = rn.getNodes();
> > ni.hasNext(); ) {
> >                                          Thread.sleep(100);
> >                                          Node currentNode = ni.nextNode();
> >                                          if
> > (currentNode.getName().startsWith("node")) {
> >                                                  currentNode.remove();
> >                                          }
> >                                  }
> >                                  session.save();
> >                                  session.logout();
> >
> >                          } catch (Exception e) {
> >                                  e.printStackTrace();
> >                          }
> >                  }
> >          }
> > }
> >
> >


Re: Problems with concurrent sessions

Posted by Stefan Guggisberg <st...@gmail.com>.
On 7/7/05, Walter Raboch <wr...@ingen.at> wrote:
> Hi,
> 
> I experience similar problems here. I start some threads with one
> session per thread and make random read/write calls with random sleep
> periods between them. As soon as there are 2 pending saves at the same
> time, I get NoSuchElementExceptions.

please post a jira issue and include a stack trace and detailed instructions 
how to reproduce the problem.

thanks
stefan

> 
> cheers,
> 
> Walter
> 
>

Re: Problems with concurrent sessions

Posted by Walter Raboch <wr...@ingen.at>.
Hi,

I experience similar problems here. I start some threads with one 
session per thread and make random read/write calls with random sleep 
periods between them. As soon as there are 2 pending saves at the same 
time, I get NoSuchElementExceptions.

cheers,

Walter

Re: Problems with concurrent sessions

Posted by Stefan Guggisberg <st...@gmail.com>.
hi nicolas

On 7/6/05, Nicolas Belisle <Ni...@bibl.ulaval.ca> wrote:
> Hi,
> 
> I'm a Jackrabbit newbie. I'm doing some tests with the tool and I'm having
> problem with concurrent sessions.
> 
> I have joined my (simple) test class (JCRTest) and the exception I get from
> running it.
> 
> Anyone had similar problems ?

this seems to be a bug, please post a jira issue.

thanks 
stefan

> 
> Regards,
> 
> Nicolas
> 
> 
> -->The exception report:
> java.util.NoSuchElementException: cdeab285-fdbc-4af3-918a-bf4316a29276
> at org.apache.jackrabbit.core.LazyItemIterator.next(LazyItemIterator.java:157)
> at
> org.apache.jackrabbit.core.LazyItemIterator.nextNode(LazyItemIterator.java:98)
> at app.JCRTest$ThreadDeleter.run(JCRTest.java:107)
> 
> -->The class:
> package app;
> 
> import java.util.Hashtable;
> 
> import javax.jcr.Node;
> import javax.jcr.NodeIterator;
> import javax.jcr.Repository;
> import javax.jcr.RepositoryException;
> import javax.jcr.Session;
> import javax.jcr.SimpleCredentials;
> import javax.jcr.observation.Event;
> import javax.jcr.observation.EventIterator;
> import javax.jcr.observation.EventListener;
> import javax.naming.Context;
> import javax.naming.InitialContext;
> 
> import org.apache.jackrabbit.core.jndi.RegistryHelper;
> import org.apache.jackrabbit.core.value.StringValue;
> 
> public class JCRTest {
> 
>          private Repository repository;
> 
>          public JCRTest(Repository repository) {
>                  this.repository = repository;
>          }
> 
>          public void execute() throws Exception {
>                  Session session = this.repository.login(new
> SimpleCredentials("", "".toCharArray()), null);
> 
>                  Node rn = session.getRootNode();
>                  new ThreadObserver().start();
>                  Node n = rn.addNode("node" + System.currentTimeMillis(),
> "nt:unstructured");
>                  n.setProperty("testprop", new StringValue("Hello, World."));
>                  new ThreadDeleter().start();
> 
>                  session.save();
>                  session.logout();
>          }
> 
>          public static void main(String[] args) {
>                  try {
>                          String configFile = "repository/repository.xml";
>                          String repHomeDir = "repository";
> 
>                          Hashtable env = new Hashtable();
>                          env.put(Context.INITIAL_CONTEXT_FACTORY,
> "org.apache.jackrabbit.core.jndi.provider.DummyInitialContextFactory");
>                          env.put(Context.PROVIDER_URL, "localhost");
>                          InitialContext ctx = new InitialContext(env);
> 
>                          RegistryHelper.registerRepository(ctx, "repo",
> configFile, repHomeDir, true);
>                          Repository r = (Repository) ctx.lookup("repo");
> 
>                          for (int i = 0; i < 20; i++) {
>                                  JCRTest test = new JCRTest(r);
>                                  test.execute();
>                          }
> 
>                  } catch (Exception e){
>                          e.printStackTrace();
>                  }
>          }
> 
>          public  class ThreadObserver extends Thread {
>                  public void run() {
>                          System.out.println("Observing thread started");
>                          try {
>                                  Session session = repository.login(new
> SimpleCredentials("", "".toCharArray()));
>                                  EventListener el = new EventListener() {
>                                          public void onEvent(EventIterator
> events) {
>                                                  while (events.hasNext()) {
>                                                          try {
>                                          Thread.sleep(100);
>                                      } catch (InterruptedException e) {}
> 
>                                      Event e = events.nextEvent();
>                                                          if (e.getType() ==
> Event.NODE_ADDED) {
>                                                                  try {
>                                                                          System.out.println("Node
> added : " + e.getPath());
>                                                                  } catch
> (RepositoryException re) {
>                                                                          re.printStackTrace();
>                                                                  }
>                                                          }
> 
>                                                  }
>                                          }
>                                  };
>                                  session.getWorkspace().getObservationManager().addEventListener(el,
> Event.NODE_ADDED, "/", true, null, null, false);
>                                  session.save();
>                                  //Some time to listen
>                                  Thread.sleep(500);
>                                  session.logout();
>                          } catch (Exception e) {
>                                  e.printStackTrace();
>                          }
>                  }
>          }
> 
>          public  class ThreadDeleter extends Thread {
>                  public void run() {
>                          System.out.println("Deleter thread started");
>                          try {
>                                  Session session = repository.login(new
> SimpleCredentials("", "".toCharArray()));
> 
>                                  Node rn = session.getRootNode();
>                                  for (NodeIterator ni = rn.getNodes();
> ni.hasNext(); ) {
>                                          Thread.sleep(100);
>                                          Node currentNode = ni.nextNode();
>                                          if
> (currentNode.getName().startsWith("node")) {
>                                                  currentNode.remove();
>                                          }
>                                  }
>                                  session.save();
>                                  session.logout();
> 
>                          } catch (Exception e) {
>                                  e.printStackTrace();
>                          }
>                  }
>          }
> }
> 
>