JCR Locks and Concurrent Modifications

20 03 2009

Heavy concurrent modification of a single node in Jackrabbit will result in InvalidItemStateException even with a transaction.
The solution is to lock the node, the code below performs a database like lock on the node, timing out after 30s if no lock was obtained. The lock needs to be unlocked as its a cluster wide lock on the node.

I suspect however that the propagation rate will not be fast enough to maintain consistency over a cluster, but then again… nothing will be fast enough without impacting performance. The slightly annoying feature of this is that you must perform locking manually. This is IMVHO a bit crazy since at some point if you don’t and you write to the node, you will get an exception, and if you are in a transaction (as you should be) you wont be able to recover the exception since it will require  rollback and a complete redo of the whole transaction.

  public Lock getNodeLock(Node node) throws UnsupportedRepositoryOperationException,
      LockException, AccessDeniedException, RepositoryException {
    Lock lock = null;
    try {
      lock = node.getLock();
      if (lock.getLockToken() != null) {
        return lock;
      }
    } catch (LockException e) {
    }
    lock = null;
    long sleepTime = 100;
    int tries = 0;
    while (tries++ < 300 ) {
      try {
        return node.lock(true, false);
      } catch (Exception ex) {
        if ( sleepTime < 500 ) {
          sleepTime = sleepTime + 10;
        }
        try {
          if ( tries%100 == 0 ) {
            System.err.println(Thread.currentThread() + " Waiting for "+sleepTime+" ms "+tries);
          }
          Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
        }
      }
    }
    throw new Error("Failed to lock node ");
  }
Advertisements

Actions

Information

4 responses

3 10 2009
Saurav

Hi ,

When we talk about the locking the nodes, which node do we lock?
We are using liferay(JCRHook.java) and while using file uploads(Jackrabbit implemetation) from concurrent users we face the exception :InvalidItemStateException Item has been modified externally node: /

The exception occurs when the session.save() is called.

Code Snippet:

public void addFile(long companyId, String portletId, long groupId,
112: long repositoryId, String fileName, String properties,
113: String[] tagsEntries, InputStream is)
114: throws PortalException, SystemException {
115:
116: Session session = null;
117:
118: try {
119: session = JCRFactoryUtil.createSession();
120:
121: Node rootNode = getRootNode(session, companyId);
122: Node repositoryNode = getFolderNode(rootNode, repositoryId);
123:
124: if (repositoryNode.hasNode(fileName)) {
125: throw new DuplicateFileException(fileName);
126: } else {
127: Node fileNode = repositoryNode.addNode(fileName,
128: JCRConstants.NT_FILE);
129:
130: Node contentNode = fileNode.addNode(
131: JCRConstants.JCR_CONTENT,
132: JCRConstants.NT_RESOURCE);
133:
134: contentNode.addMixin(JCRConstants.MIX_VERSIONABLE);
135: contentNode.setProperty(JCRConstants.JCR_MIME_TYPE,
136: “text/plain”);
137: contentNode.setProperty(JCRConstants.JCR_DATA, is);
138: contentNode.setProperty(JCRConstants.JCR_LAST_MODIFIED,
139: Calendar.getInstance());
140:
141: session.save();
142:
143: Version version = contentNode.checkin();
144:
145: contentNode.getVersionHistory().addVersionLabel(
146: version.getName(),
147: String.valueOf(DEFAULT_VERSION), false);
148:
149: Indexer
150: .addFile(companyId, portletId, groupId,
151: repositoryId, fileName, properties,
152: tagsEntries);
153: }
154: } catch (IOException ioe) {
155: throw new SystemException(ioe);
156: } catch (RepositoryException re) {
157: throw new SystemException(re);
158: } finally {
159: if (session != null) {
160: session.logout();
161: }
162: }
163: }

5 10 2009
Ian

The InvalidItemStateException can be caused by lots of things, like accessing properties within the AccessManager, however for the moment, assuming InvalidItemStateException is caused by 2 thread failing to merge changes correctly. You will need to lock he parent of the item where you are making the modification. For instance, if adding a new child, then the parent of the child will need to be locked since children are stored in an array property. In the case of a file upload, I would lock the parent folder, as this might be a new file upload.

The second thing that I would point out is that JCR locking propagates over a cluster and if you believe that you wont run in a cluster or you believe that the lock/unlock operation are going to be so close together in the same request that propagation to a cluster has no value, then a simple JVM based lock might make more sense, eg the path of the ode to be locked in a shared concurrent hash map. IMHO JCR locks are more suited to where the application wants to take an explicit lock on the node, or where you know that the time which uncommitted changes last is > 20ms.

18 04 2011
Jason

Why dont you just use the lock utility from JackRabbit? Then you don’t have to manually manage the locking mechanism .

http://jackrabbit.apache.org/api/2.2/org/apache/jackrabbit/util/Locked.html

18 04 2011
Ian

Locked will work just fine in a single JVM, although I think it will write the lock to the JCR. The big problem is a cluster is there is no way the locking event will propagate fast enough to make the locking effective. The second issue with a cluster, at least with the JDBC implemented Journal is that the whole cluster becomes single threaded on update as Events require a RDBMS lock to be taken on a single central table, only released when the JDBC transaction is committed, so yes, Locked prevents Concurrent Modifications at the expense of almost all write scalability. The solution I have used to use Cages or JGroups to manage cluster wide locks, in memory and distributed over IP. Still, all this locking and unlocking does impact performance and it would be far better to be able to add child nodes with high concurrency.




%d bloggers like this: