Hi Ard,
I've not tried the double-aggregate thing (yet), but I've now attached
to the bug a very simple repeatable test that demonstrates the lock up
as I've experienced it.
Have fun!
Ellis.
Ard Schrijvers wrote:
Hi,
The crux is that the sub-pipeline is called twice within the
context of
the master pipeline (once by the root pipeline, once by an include);
thus the pipeline keys which are the same are those for the
sub-pipeline, not the master pipeline.
My 'broken' pipeline is too complex to explain, but it's basically
something like:
If this results in a deadlock, then there is something basically wrong with this locking. Does the master pipeline lock its subpipeline untill it is finished itself? That wouldn't make sense.
I mean, for the thing below, the master would have a lock related to the cachekey where the cachekey is something like:
PK_G-file-cocoon:/foo?pipelinehash=-3411761154931530775_T-xslt-/....page.xsl_S-xml-1
Then, as I would understand this key is locked. Now, the pipeline with
pattern="foo" gets its own pipeline cachkey, which is also locked. But after
this one is finished, your problem indicates that the lock of this sub pipeline is not
cleared untill the master pipeline is finished? This doesn't make sense to me.
Furthermore, if this setup gives problems, then wouldn't
<map:aggregate>
<map:part src="cocoon:/foo">
<map:part src="cocoon:/foo">
</map:aggregate>
result in the same deadlock? I must be missing something trivial
Ard
<map:match pattern="master">
<map:generate src="cocoon:/foo">
<map:transform src="page.xsl"/> <!-- generates include element for
"cocoon:/included" -->
<map:transform type="include"/> <!-- includes "included"
sub-pipeline -->
<map:serialize/>
</map:match>
<map:match pattern="included">
<map:generate src="cocoon:/foo">
<map:transform src="included-page.xsl"/>
<map:serialize/>
</map:match>
<map:match pattern="foo"> <!-- this gets called twice -->
<map:generate ... />
<map:serialize/>
</map:match>
Ellis.
Ard Schrijvers wrote:
Hello,
Cocoon 2.1.9 introduced the concept of a lock in
AbstractCachingProcessingPipeline, an optimization to prevent
two concurrent requests from generating the same cached
content. The first request adds the pipeline key to the
transient cache to 'lock' the cache entry for that pipeline,
subsequent concurrent requests wait for the first request to
cache the content (by Object.lock()ing the pipeline key
entry) before proceeding, and can then use the newly cached content.
However, this has introduced an incompatibility with the
IncludeTransformer: if the inclusions access the same
yet-to-be-cached content as the root pipeline, the whole
assembly hangs, since a lock will be made on a lock already
held by the same thread, and which cannot be satisfied.
e.g.
i) Root pipeline generates using sub-pipeline cocoon:/foo.xml
ii) the cocoon:/foo.xml sub-pipeline adds it's pipeline key
to the transient store as a lock.
iii) subsequently in the root pipeline, the
IncludeTransformer is run.
iv) one of the inclusions also generates with
cocoon:/foo.xml, this sub-pipeline locks in
AbstractProcessingPipeline.waitForLock() because the
sub-pipeline key is already present.
v) deadlock.
I do not understand one part of it. If a sub-pipeline is
called, cocoon:/foo.xml, there is lock generated for this
sub-pipeline seperately, right? (if not, I do not understand
why it is not like this. I suppose a lock is generated for
the root pipeline, but as well for every sub-pipeline
individually. I suppose though, because i did not actually
look at the code).
Now, if the include transformer calls this same
sub-pipeline, which is having its own lock, I do not see why
a deadlock can occur? The root-pipeline is locked, the
sub-pipeline is locked as well. The include transformer wants
to include the same sub-pipeline, waits untill this one is
finished, then can includes it, right?
I most be missing something,
Regards Ard
I've found a (partial, see below) solution for this: instead
of a plain Object being added to the transient store as the
lock object, the Thread.currentThread() is added; when
waitForLock() is called, if the lock object exists, it checks
that it is not the same thread before attempting to lock it;
if it is the same thread, then waitForLock() returns success,
which allows generation to proceed. You loose the efficiency
of generating the cache only once in this case, but at least
it doesn't hang! With JDK1.5 this can be made neater by using
Thread#holdsLock() instead of adding the thread object itself
to the transient store.
See patch file.
However, even with this fix, parallel includes (when enabled)
may still hang, because they pass the not-the-same-thread
test, but fail because the root pipeline, which holds the
initial lock, cannot complete (and therefore statisfy the
lock condition for the parallel threads), before the threads
themselves have completed, which then results in a deadlock again.
The complete solution is probably to avoid locking if the
lock is held by the same top-level Request, but that requires
more knowledge of Cocoon's processing than I (currently) have!
IMHO unless a complete solution is found to this, then this
optimization should be removed completely, or else made
optional by configuration, since it renders the
IncludeTransformer dangerous.
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the
administrators:
https://issues.apache.org/jira/secure/Administrators.jspa
-
For more information on JIRA, see:
http://www.atlassian.com/software/jira