CQ development anti-pattern: Single session, multiple threads

The simple way to use relevant feature sof OSGI, combined with the power of Declarative Services (SCR), lead often to a very simple design of services. You can just define the service interface as a pure Java interface, and then implement your service as a single class with just some annotation. You can handle the complete live cycle of it in this single class.

@Component(immediate=true)
@Service
public class myServiceImpl implements MyService {

  @Activate
  protected void activate () {
  …
  }

  @Deactivate
  protected void deactivate() {
    …
  }

}

And that’s it. That’s a very simple approach, which can satisfy the requirements of most services.

But it can lead to an anti-pattern, which can be problematic. The problem is the fact, that it’s easy to acquire resources on activate and release them again on deactivate(). That’s especially true with JCR sessions.

@Reference
SlingRepository repo;

Session adminSession = null;

@Acticate
protected void activate(ComponentContext ctx) {
  try {
    adminSession = repo.loginAdministrative(null);
  } catch (RepositoryException e) {
  ...
  }
}

@Deactivate 
void deactivate(ComponentContext ctx) {
  if (adminSession != null && adminSession.isLive()) {
    adminSession.logout();
  }
}

So during the whole lifetime of this service you have a JCR (admin-) session, which is just available and you don’t need to have a special handling for it. It’s just there and it’s supposed to be open and alive (unless something really weird happens). Your service methods can now be as simple like this:

public void doSomething() {
// work with the adminSession
}

So, what’s the problem then? Why do I call it an anti-pattern?

Basically, it’s the use of a single session; your service might be called by multiple threads in parallel, and you might suppose, that these calls can be processed in parallel. Well, that isn’t the case. In the current implementation of Jackrabbit 2.x the JCR session has an internal lock, which prevents multiple sessions to work in parallel on the very same session (both read and write)! So by this design you limit your application’s scalability, because the threads are queuing up and wait until they get hold of that internal lock. And this is something we really should avoid.

So, what’s the best method to avoid this? That’s quite simple: Use a new session for each call. Sessions are cheap and don’t require a relevant startup time. I already presented this pattern in the context of preventing memory leaks and I want to repeat it here:

public void doSomething() {
  Session adminSession = null;
  try {
    adminSession = repo.loginAdministrative(null);
    // do something useful here
  } catch (RepositoryException e) {
    // error handling
  } finally {
    if (adminSession != null && adminSession.isLive()) {
      adminSession.logout;
    }
  }
}

Here you have a dedicated session for each call (and implicit for each thread calling this method), you never need to bother with this kind of locking issues.

A hint to find such bottlenecks: Set the log level of the class org.apache.jackrabbit.core.session.SessionState to DEBUG; if you have concurrent access to the same session from multiple threads, it will write a statement to the log.

Advertisements