Project

Profile

Help

Bug #2220

closed

Multithreading: concurrent creation of "prior pointers" in TinyTree

Added by Michael Kay over 9 years ago. Updated over 8 years ago.

Status:
Closed
Priority:
Normal
Assignee:
Category:
Multithreading
Sprint/Milestone:
-
Start date:
2014-11-14
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
Fix Committed on Branch:
Fixed in Maintenance Release:
Platforms:

Description

Reported today by private email, an intermittent fault with a stacktrace like this:

net.sf.saxon.trans.XPathException: java.lang.ArrayIndexOutOfBoundsException: 7961

    at com.saxonica.stream.SequenceExchanger$EvaluationThread.run(SequenceExchanger.java:225)

    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)

    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)

    at java.lang.Thread.run(Thread.java:662)

Caused by: java.lang.ArrayIndexOutOfBoundsException: 7961

    at net.sf.saxon.tree.tiny.TinyTree.makePriorIndex(TinyTree.java:770)

    at net.sf.saxon.tree.tiny.TinyTree.ensurePriorIndex(TinyTree.java:760)

    at net.sf.saxon.tree.tiny.PrecedingSiblingEnumeration.<init>(PrecedingSiblingEnumeration.java:31)

    at net.sf.saxon.tree.tiny.TinyNodeImpl.iterateAxis(TinyNodeImpl.java:491)

    at gen_CompiledFilterIterator_3.matches(file:///opt1/mcs_dev/datafiles/xslt/current/stressBasedMargin.xslt:65535)

    at com.saxonica.bytecode.iter.CompiledFilterIterator.next(CompiledFilterIterator.java:49)

    at net.sf.saxon.value.SequenceExtent.<init>(SequenceExtent.java:112)

    at net.sf.saxon.expr.sort.DocumentOrderIterator.<init>(DocumentOrderIterator.java:38)

I think the stack trace makes it clear what's going on. It's not one we've seen before.

There are concurrent threads running, and the code is putting "prior" pointers into the tinytree. These pointers are only needed if the code uses a backwards axis (preceding-sibling), so to save space, they are only created when first needed. I suspect the "first need" is occurring within parallel threads so two threads are trying to create the prior pointers at the same time, causing data corruption and an arbitrary crash.

Found in 9.5.1.8 but the problem is likely to be present also in earlier and later releases

Actions #1

Updated by Michael Kay over 9 years ago

  • Project changed from 4 to Saxon
  • Category set to Multithreading
Actions #2

Updated by Michael Kay over 9 years ago

The method that builds the prior pointer index is synchronized, so it's not a completely crass oversight.

I suspect it has something to do with the way that a single TinyTree data structure is used to hold a sequence of separate trees (a forest). Although the creation of a prior index is synchronized, the addition of new trees to the forest is not.

Actions #3

Updated by Michael Kay over 9 years ago

There's certainly a theoretical problem here, which may or may not be the cause of this crash. A SequenceOutputter can be reused to write a sequence of trees to the same TinyTree structure; this process is synchronized on the Controller to make it thread-safe. But the process which allocates prior pointers is synchronized on the TinyTree itself, so the two processes are not protected from each other. The latter process accesses the TinyTree field numberOfNodes, which can change value if someone else is appending to the tree while the index is being built, and indeed the AIOOB exception strongly indicates that this is happening.

I'll try and create a test case to trigger this condition so that I can test any fix.

There are several possible approaches to fixing it. One (already suggested by a TODO in the code) is to build the prior index only for one document at a time. Another is to synchronize both operations on the same object. Another is to abandon the idea of re-using the SequenceOutputter and the TinyTree structure for multiple trees (I recall the performance benefits were significant at the time, but this is not necessarily still the case, especially as we are probably now smarter in terms of deciding how much space o allocate).

It may be a case where the "quick fix" solution for a maintenance release is different from the permanent solution.

Actions #4

Updated by Michael Kay over 9 years ago

I have reproduced the problem with the following stylesheet:

<xsl:stylesheet version="2.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:t="this"> 
  <xsl:output indent="yes"/>
  <xsl:template match="/" name="main">
    <out>
     <xsl:for-each select="1 to 1000" saxon:threads="4" xmlns:saxon="http://saxon.sf.net/" exclude-result-prefixes="saxon">
       <xsl:variable name="temp" as="element()">
         <doc>
           <xsl:for-each select="1 to .+50">
              <ele><xsl:value-of select="."/></ele>
           </xsl:for-each>
         </doc>
       </xsl:variable>
       <in top="{name($temp)}"><xsl:value-of select="count($temp/ele[last()]/preceding-sibling::*)"/></in>
     </xsl:for-each>
    </out> 
  </xsl:template>
</xsl:stylesheet>
Actions #5

Updated by Michael Kay over 9 years ago

I have a fix which appears to solve the problem: the code for building the prior index is changed so that it stops at the end of the document (tree) containing the node whose preceding-sibling is required. (It also indexes all previous documents in the tree if not already done). I will now regression test this to check for side-effects.

Actions #6

Updated by Michael Kay over 9 years ago

I have run the test repeatedly and unfortunately the original problem reoccurs; it seems my patch reduces the probability of hitting problems but does not eliminate it.

Actions #7

Updated by Michael Kay over 9 years ago

Performance experiment: if I run single-threaded, execution time is

with reuse of SequenceOutputter enabled: 237.34ms

with reuse of SequenceOutputter disabled: 224.82ms

So for this workload at least, reuse of the SequenceOutputter (and hence the TinyTree structure) is delivering no benefit. This suggests the simplest fix might be to disable such reuse. (This fixes the problem because the TinyTree is then immutable after construction, and the prior pointer index can then safely be built for the entire tree.)

If I run with reuse of SequenceOutputter disabled, in 4 threads, execution time is 96.15ms; with 8 threads it is 83.98ms; with 12 threads it is 87.71ms.

Actions #8

Updated by Michael Kay over 9 years ago

I ran the XMark benchmark with and without reuse of TinyTree enabled and there was no appreciable difference. So I think the answer is to eliminate this code.

Actions #9

Updated by Michael Kay over 9 years ago

  • Status changed from In Progress to Resolved

I have committed this change on the 9.5, 9.6, and 9.7 branches. The test case has been added to xslt30extra as test threads-021.

Actions #10

Updated by Michael Kay over 9 years ago

  • Status changed from Resolved to In Progress

Reopening because the customer reports that there are still problems after applying the patch.

Actions #11

Updated by Michael Kay over 8 years ago

  • Status changed from In Progress to Closed
  • Fixed in version set to 9.6

There have been no further reports of problems for a year, so I am going to assume that the patch solved the problem, and I am closing the bug report.

Please register to edit this issue

Also available in: Atom PDF