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
|
| |
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.
|
| |
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:
-
The
fetchComponent method checks to see if the component exists
and returns it if it does. If it does not, it calls fetchNewComponent.
-
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.
|
| |
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 .
|
|