uPortal Concurrency Services
uPortal Concurrency Services
Contents
Introduction
Goals and Rationale
Package Structure
Caching API
Locking API
Using the Caching and Locking Services
Notes on Service Implementations
Introduction
The concurrency packages, org.jasig.portal.concurrency.*, are offered as building blocks for portal developers who want to create persistence support for portal entities. The locking and caching packages are self-contained "mini-frameworks" exposed as portal services via classes in org.jasig.portal.services. Â
The concurrency framework operates on entities, persistent objects uniquely identified by a type and a key. As currently implemented, these packages cache entities in memory for fast access and lock entities for either reading or writing. Perhaps in the future, we will abstract the operations of creating, finding and updating them.  Goals and Rationale
The need for these services arose because of performance problems in the groups framework. Groups are relatively expensive to create and are frequently reused. They needed to be cached, and since they are updatable, they also needed to be locked. Caching and locking objects in a single JVM is fairly simple, but it quickly gets complicated when the caching and locking must work across multiple JVMs and servers.  Â
There are some decent commercial caching products available, but they are not appropriate for an open source project. Unfortunately, the open source solutions I looked at were too big, too ugly, too early or just not right (I know, picky, picky.)  I think the most promising is Turbine's Java Caching System or JCS (see http://jakarta.apache.org/turbine/jcs), an approximate implementation of the JCache specification (see http://jcp.org/jsr/detail/107.jsp), which in turn, describes the Oracle9i caching framework. JCS promises to be an efficient, full-featured framework, but it is still in development and it does not do locking. So, at least in the interim, uPortal needs some home grown locking and caching support. The concurrency packages are intended as a common solution that can be used not just by groups but by any type of portal entity and replaced when a better alternative is found. They are not intended to replace or compete with full-featured commercial or open source frameworks.  Package Structure
The concurrency services operate on IBasicEntities, objects that implement org.jasig.portal.IBasicEntity, defined as follows:
public interface IBasicEntity { public org.jasig.portal.EntityIdentifier getEntityIdentifier(); }
An EntityIdentifier has a key and type that uniquely identify its underlying entity. Â Two EntityIdentifiers with the same key and type refer to the same underlying entity (although they may belong to different IBasicEntities.)
The package org.jasig.portal.concurrency defines some basic types to support concurrency: a lock (IEntityLock), a cache (IEntityCache), and services for locking and caching (IEntityLockService and IEntityCachingService). It also contains the locking and caching Exceptions:
org.jasig.portal.concurrency
IEntityCache
IEntityCachingService
IEntityCachingServiceFactory
IEntityLock
IEntityLockService
IEntityLockServiceFactory
CachingException
LockingException
The individual locking and caching packages contain reference implementations of the basic types, plus some supporting classes.
org.jasig.portal.concurrency.caching CachedEntityInvalidation
LRUCache
RDBMCachedEntityInvalidationStore
ReferenceEntityCache
ReferenceEntityCachingService
ReferenceEntityCachingServiceFactory
ReferenceInvalidatingEntityCache
org.jasig.portal.concurrency.locking
EntityLockImpl
IEntityLockStore
MemoryEntityLockStore
RDBMEntityLockStore
ReferenceEntityLockService
ReferenceEntityLockServiceFactory
Caching API
IEntityCachingService defines a minimal api for caching and retrieving IBasicEntities. An IEntityCachingService manages a number of IEntityCaches.  Each IEntityCache holds IBasicEntities of a given type, (the type returned by IEntityCache.getEntityType()).  This type must be known to the portal (see org.jasig.portal.EntityTypes.) In a multi-server environment, caches must be able to synchronize themselves with their peers on other servers, but this is part of their implementation and not part of the api:
public interface IEntityCachingService { void add(IBasicEntity entity) throws CachingException; IBasicEntity get(Class type, String key); void remove(Class type, String key); void update(IBasicEntity entity) throws CachingException; }
Locking API
The interface IEntityLockService defines an api for acquiring lock objects, IEntityLocks, that can be used to control concurrent access to IBasicEntities. The sub-type of IBasicEntity must be known to the portal (see org.jasig.portal.EntityTypes.) A lock is associated with a particular entity and has an owner, a lockType and an expirationTime. Currently supported lock types areIEntityLockService.READ_LOCK, which is shared, and IEntityLockService.WRITE_LOCK, which is exclusive.  To lock an entity for update, ask the service for a write lock:
int lockType = IEntityLockService.WRITE_LOCK; IBasicEntity entity = getEntityFromSomewhere(); IEntityLock lock = svc.newLock(entity, lockType, lockOwner);
If there is no conflicting lock on the entity, the service responds with the requested lock. If another lock owner holds a conflicting lock, the service throws a LockingException. If the service returns the lock, no other client will get be able to get a conflicting lock. From then on, communication with the service is via the lock:
lock.convert(int newType); lock.isValid(); lock.release(); lock.renew();
The complete service api is as follows:
public interface IEntityLockingService { public void convert(IEntityLock lock, int newType) throws LockingException; public void convert(IEntityLock lock, int newType, int durationSecs) throws LockingException; public boolean existsInStore(IEntityLock lock) throws LockingException; public boolean isValid(IEntityLock lock) throws LockingException; public IEntityLock newLock(IBasicEntity entity, int lockType, String owner) throws LockingException; public IEntityLock newLock(IBasicEntity entity, int lockType, String owner, int durationSecs) throws LockingException; public void release(IEntityLock lock) throws LockingException; public void renew(IEntityLock lock) throws LockingException; public void renew(IEntityLock lock, int durationSecs) throws LockingException; }
Using the Caching and Locking Services
The caching and locking APIs are accessed by portal clients via service façades located in org.jasig.portal.services, EntityCachingService and EntityLockService. These classes serve as bootstraps for service implementations specified in portal.properties.  One way to plug in an external locking or caching service would be to create an alternate implementation of the service that adapts the external service to the portal interface.
The caching façade simply duplicates the api of the service interface. Caching consists of asking the service to add, retrieve, update and remove elements from the cache, e.g.:
// Retrieve the entity from its store: String key = getEntityKey(); IBasicEntity ent = findEntity(key); // Cache the entity: EntityCachingService.add(ent); ... // Retrieve the entity from the cache: Class type = getEntityClass(); IBasicEntity anotherReferenceToTheEntity = EntityCachingService.get(type, key); ... // Change the entity and then notify the cache: EntityCachingService.update(ent); // notifies peer caches. // Or delete the entity and notify the cache: EntityCachingService.remove(type, key); // notifies peer caches.
The EntityLockService façade lets clients do one single thing, acquire a lock on an entity:
IBasicEntity entity = getEntityFromSomewhere(type, key); String owner = getThePortalUserId(); IEntityLock lock = EntityLockService.instance().newWriteLock(entity, owner); Or perhaps: IEntityLock lock = EntityLockService.instance().newWriteLock(entity, owner, durationSecs);
Once the client has the lock, it communicates with the underlying service implementation via the lock: lock.convert(IEntityLockService.WRITE_LOCK); // convert read to write ... if ( lock.isValid() ) // check if lock has expired { entity.update(); lock.release(); }
Notes on Service Implementations
Common properties. The portal.properties file has a section labelled "Concurrency Services settings" with values for configurable concurrency properties.  There are 2 properties common to concurrency services, multi-server, which indicates if the portal will run in multiple JVMs, and clockTolerance, which can be set to account for differences among system clocks on different hosts:
- Concurrency Services settings:
# - multiServer (true/false) indicates if the portal will run in multiple jvms.
# - clockTolerance (in milliseconds) sets a fudge factor to account for system clocks
- on different hosts. Only used when org.jasig.portal.concurrency.multiServer=true.
# - Defaults: multiServer=false
- clockTolerance=5000
#
org.jasig.portal.concurrency.multiServer=false
org.jasig.portal.concurrency.clockTolerance=5000
These properties apply to both concurrency services. The full list of properties is:
Service |
Property |
Description |
---|---|---|
concurrency |
multiServer |
Indicates if the portal will run in multiple JVMs. Default=false. |
concurrency |
clockTolerance |
Fudge factor in milliseconds for system clocks. Only used when multiServer=true. Default=5000. |
Locking |
IEntityLockServiceFactory |
Service factory. efault=ReferenceEntityLockServiceFactory. |
Locking |
defaultLockDuration |
Default value in seconds, may be overridden at request time. default=300. |
Caching |
IEntityCachingServiceFactory |
Service factory. default=ReferenceEntityCachingServiceFactory |
Caching |
defaultMaxCacheSize |
Default for maximum number of cache entries. Can be overriden for a specific cache type. Default=1000. |
Caching |
maxCacheSize |
(Optional) Maximum number of cache entries for a specific cache. See portal.properties for examples. |
Caching |
defaultSweepInterval |
Default in seconds for interval between sweeps. Can be overridden for a specific cache type. Default=60. |
Caching |
sweepInterval |
(Optional) Interval in seconds between sweeps for a specific cache. See portal.properties for examples. |
Caching |
defaultMaxIdleTime |
Default in seconds for the maximum time a cache entry can remain untouched before being reaped. Can be overridden for a specific cache type. Default=1800. |
Caching |
maxIdleTime |
(Optional) Maximum time in seconds a cache entry can remain untouched before being reaped, for a specific cache. See portal.properties for examples. |
Single-server vs. multi-server. In single-server mode, locks are held in memory.  There is no need to invalidate entries in peer caches because there are no peer caches.  In multi-server mode, locks are stored in the portal database, which is shared by all portal instances. Each time a cache entry is updated or removed, the cache adds an invalidation to the invalidation store, which is likewise held in the portal database. Caches periodically retrieve invalidations from the store and purge invalid entries.
Multi-server mode and pooled database connections. If the portal is running on a single server, concurrency services should run in single-server mode, since running in multi-server mode imposes extra database activity. However, if the portal is running in a multi-server environment, you must run in multi-server mode for locking and caching to function properly. In multi-server mode, these services make frequent hits to the portal database using connections obtained from org.jasig.portal.RDBMServices. As a result, RDBMServices must have a source of pooled database connections, or else the concurrency services themselves will become performance bottlenecks.
The Cache Cleanup Cycle. Each cache instance runs a cleanup thread that periodically wakes up and invokes a cleanup routine to purge the cache of stale entries. The interval between cleanups is configurable in portal.properties. In multi-server mode, the cache first retrieves and processes its invalidations. In either case, if the cache is larger than its maximum size, the cleanup routine removes least recently used cache entries until the cache no longer exceeds its maximum size.
Future enhancements.  Caches are now created as needed. Perhaps they should be created and pre-populated on service start-up, kicked off by an entry in services.xml.  The caching properties might be better represented in xml format.
de 9/02/2003