The Carbon Java Framework  

The Carbon Core Component Subsytem

Fetching New Components

Author: Doug Voet (dvoet at sapient.com)
Version: $Revision: 1.3 $($Author: dvoet $ / $Date: 2003/04/09 15:05:09 $)
Created: February 2002

Introduction

This technote describes how the requirements of 1) "Create component only once" and 2) "Component creation should not block retrieval of other components" are fulfilled by the DefaultComponentKeeper. It also describes how circular component references are handled.

Concurrent Component Requests

Requirement 1 implies that if multiple threads request the same component concurrently and the component has not yet been created, only 1 thread creates the component while the other threads wait. Requirement 2 implies that while the threads mentioned above are waiting, threads requesting other components are not forced to wait.

For example, say there are 5 threads, T1 through T5, and 3 components C1 through C3, in the system. T1 and T2 request C1 before it has been created. T2 is arbitrarily chosen to create C1. T1 then waits for T2 to finish creating C1. If C1 takes, for example, 30 minutes to create, neither T1 nor T2 will return for 30 minutes. In the mean time, in a similar situation T3 and T4 request C2 before it has been created. If T3 is chosen to create C2, T4 must wait for T3, however neither T3 nor T4 need to wait for T1 or T2. Then, while T1 is waiting for T2, and T4 is waiting for T3, T5 comes along and asked for C3 which already exists. T5 gets a reference to C3 without waiting for any other thread.

This logic is handled in the following fashion:

  1. The fetchComponent method checks to see if the component exists and returns it if it does. If it does not, it calls fetchNewComponent.
  2. fetchNewComponent gets a lock object from creationLocks and synchronizes on it. Once it enters the synchronized block, it must check to see if the component has been created as another thread may have gotten the lock and synchronized first. If the component still does not exist, it is created using the component factory.

Cicular References

Circular references are a problem due to two factors:

  • fetchComponent should only return components that are in a running state
  • Dependancies on other components are resolved during configuration which happens before the component is placed in a running state

The initial process for creating components placed components in the componentMap only after it had been configured and started. This caused an infinite loop. For example, consider the circular structure, component A references B which references A. A is requested, which causes a request for B in A's configuration step, which then requests A in B's configuration step. At this point, A is not running so the ComponentKeeper thinks it needs to create a new component A and the cycle continues. This cycle needed a short circuit to detect that A is in the creation process.

The solution is to use a Map called nascentComponents. nascentComponents is a Map of components that have been constructed and initialized, but not configured or started (the state of the component when returned from the ComponentFactory). When a component is returned from the ComponentFactory, it is placed in nascentComponents first, then configured and started. At this point, subsequent fetches for the same component on the same thread look in nascentComponents before deciding to create the component, and if is is there, it is returned. If the second request is on a different thread, it waits for the component to be started because it needs to synchronize on A's component lock.

The first interesting note is that circular references can only be resolved on a single thread (see Circular Component References in Threading Concerns). The second interesting note is that in the configuration step of a component in a circular reference, referenced components are not guaranteed to be in a running state. This means that the configure lifecycle method should not make calls to referenced components. For example, consider again the A->B->A structure. Initially, neither A nor B have been constructed, then a request for A come along. During A's configuration but before A is started, B's configuration causes another request for A. At this point, A is in a stopped state; all functional interface calls will throw a ComponentUnavailableException.


Copyright © 2001-2003, Sapient Corporation