When instrumenting concurrency in any application, you must take responsibility for locking certain objects to ensure thread integrity. Even if you don’t instrument concurrency in an application, that application still uses multiple threads—no Java application is truly single-threaded. On most Java virtual machines, the garbage collector and finalize methods each run in separate threads. To ensure that objects you manipulate aren’t affected by one of these threads, you must lock the objects you manipulate directly. As part of instrumenting concurrency in an Enterprise Objects application, but also in Enterprise Objects applications in which you don’t explicitly instrument concurrency, it is your responsibility to lock the Enterprise Objects you use directly.
In versions of Enterprise Objects prior to WebObjects 5.2, you were expected to explicitly lock and unlock EOEditingContext objects. Most other Enterprise Objects locked themselves in any methods that changed state (which includes most methods) or did not support locking.
These objects, however, which primarily include EOObjectStore objects and fault handlers in the access layer, had no way of knowing the context of their usage. The breadth of the Enterprise Objects API allowed them to be used in many different ways at many different times. For example, faults can be fired in many different scenarios. Consequently, these objects needed to lock and unlock frequently. This has undesirable performance characteristics.
Imagine an EOEditingContext fetches a thousand rows from a database. The EODatabaseContext method initializeObject is invoked once per row to create a corresponding EOEnterpriseObject. That method performs locking operations each time it is invoked. Since EODatabaseContext can service only one EOEditingContext at a time, nearly all of those locking operations are redundant.
A better design is to remove the locking operations from initializeObject and to mandate that the locking operations are instead performed on the EODatabaseContext object. Then, EOEditingContext manages the lock on the EODatabaseContext it uses to fetch data. Only a single set of locking operations on an EODatabaseContext object is required per fetch.
In this design, object stores automatically lock their parent object stores when they perform operations requiring access to those object stores. So, an editing context locks its parent object store once per fetch, making it unnecessary for locking operations to occur in each invocation of initializeObject.
This design reflects the updated concurrency architecture of Enterprise Objects in WebObjects 5.2. In the updated architecture, each lock is treated as a shared resource. To ensure safe concurrent access to Enterprise Objects, it is your fundamental responsibility is to lock the Enterprise Objects you use directly. The Enterprise Objects you manipulate directly and lock then lock any additional resources they use directly as needed.
Not all classes in the core Enterprise Object layers are suitable for concurrent access within this locking paradigm—this includes Enterprise Object classes that do not implement the NSLocking interface.
For example, EOEnterpriseObject does not implement the NSLocking interface. The framework assumes that enterprise object instances are used only by the thread that has locked their EOEditingContext. Since it probably never makes sense to provide concurrent access to EOEnterpriseObject instances separately from their EOEditingContext, it shouldn’t be a problem that EOEnterpriseObject does not implement NSLocking.
You usually interact only with the EOEditingContext lock. It is vital to properly lock and unlock EOEditingContext objects to ensure the integrity of their EOEnterpriseObject instances. An editing context (an object store) automatically locks its parent object store (usually an instance of EOObjectStoreCoordinator). Obtaining a lock on an EOObjectStoreCoordinator causes it to lock all of its registered cooperating object stores.
Since EOObjectStoreCoordinator is the highest-level object in the access layer stack, and since it automatically locks the object stores registered with it, obtaining a lock on an EOObjectStoreCoordinator is sufficient to manipulate any access-layer objects underneath it. In other words, objects in the access layer can be used only by the thread that has obtained a lock on the object store coordinator that is the highest-level object in that particular access layer stack—you must secure a lock on a particular object store coordinator before using any of the objects it manages.
When you secure a lock for an object store coordinator, it automatically locks all of its registered cooperating object stores. When you release the lock on a particular object store coordinator, it automatically releases the locks it has on its cooperating object stores. If you directly manipulate any access layer objects that are not cooperating object stores that have been locked by an object store coordinator, you must lock and unlock those objects yourself. This includes objects like EODatabaseChannel and EOAdaptorChannel.
What are some of the consequences of irresponsible locking? Consider the case of an invocation of the invalidateAllObjects method on an editing context. This method propagates to every EOEditingContext in an application. If an unlocked EOEditingContext receives an invalidateAllObjects method, that editing context’s EOEnterpriseObjects are forcefully turned into empty faults. The most favorable outcome of this scenario is that any outstanding changes in the application are lost. You can see that it’s important to lock editing contexts (or other Enterprise Objects) to ensure that operations intended for a particular editing context (or other type of Enterprise Object) don’t adversely affect other editing contexts.
Part of the responsibility in managing locks on Enterprise Objects is to responsibly discard locks. You must unlock any locks you explicitly obtain regardless of the circumstances. You can use finally blocks to achieve this requirement. Leaving locks in place after a nonfatal exception eventually deadlocks the application. Locked editing contexts can be garbage collected, so removing references to editing contexts also releases their locks.
Here are a few additional guidelines regarding locking in Enterprise Objects applications:
In general, you should first secure the appropriate locks on EOObjectStoreCoordinators before posting notifications that other Enterprise Objects register to receive.
Enterprise Object delegates do not need to worry about locking unless they attempt to access additional resources.
Enterprise Objects uses more sophisticated locking objects than those built in to Java. These objects provide both you and Enterprise Objects with more control over the scope of critical regions within applications. This reduces contention and the possible scenarios that can generate deadlocks.
Child (nested) editing contexts use their parent’s lock.
EOSharedEditingContext objects have a multireader, single-writer lock.
Each EOObjectStore instance and each EOObjectStoreCoordinator instance may have its own lock.
There is a global lock for loading EOModel files.
Problems with locking can be addressed by using NSLog. Set the debug level to at least DebugLevelInformational and the debug groups to include DebugGroupMultithreading. In the event of apparent deadlock, you can obtain a complete stack trace of all the threads within the Java virtual machine by sending the java process the QUIT signal. You can do this on the command line with kill -3pid or Control \, although these commands vary by Java platform.
Last updated: 2007-07-11