There is 1 update.
 
 
Confluence / cid:jira-generated-image-avatar-0e1c7a73-ac49-4ff7-91e4-8ad8f9cb7c10 CONFLUENCE-533 Open

Endless Loop in ConfluenceConverterListener / ConfluenceWrappingListener

 
View issue   ยท   Add comment
 

1 update

 
cid:jira-generated-image-avatar-507b14f5-aeb4-411e-8746-13e1af03ddd1 Changes by Gunter Ohrner on 30/Jul/26 23:56
 
Description: (Not sure about the version.)

A Confluence XML import can run into an endless loop. *It makes importing affected spaces impossible.*

We have not yet pinpointed the problem down exactly and don't know the affected page yet (we don't have direkt access to the affected XWiki instance), but the stacktrace when the importer got into the endless loop looks as follows:
{code:none}
   java.lang.Thread.State: RUNNABLE
at org.xwiki.rendering.listener.WrappingListener.onWord(WrappingListener.java:299)
at org.xwiki.rendering.listener.chaining.EventType$51.fireEvent(EventType.java:645)
at org.xwiki.rendering.listener.QueueListener.consumeEvents(QueueListener.java:96)
at org.xwiki.contrib.confluence.filter.internal.input.ConfluenceConverterListener.endParagraph(ConfluenceConverterListener.java:252)
at org.xwiki.rendering.listener.chaining.EventType$6.fireEvent(EventType.java:103)
at org.xwiki.contrib.confluence.parser.xhtml.internal.wikimodel.ConfluenceXWikiGeneratorListener.fireEvents(ConfluenceXWikiGeneratorListener.java:605)
at org.xwiki.contrib.confluence.parser.xhtml.internal.wikimodel.ConfluenceXWikiGeneratorListener.fireEvents(ConfluenceXWikiGeneratorListener.java:627)
at org.xwiki.contrib.confluence.parser.xhtml.internal.wikimodel.ConfluenceXWikiGeneratorListener.handleListItem(ConfluenceXWikiGeneratorListener.java:452)
at org.xwiki.contrib.confluence.parser.xhtml.internal.wikimodel.ConfluenceXWikiGeneratorListener.onMacroBlock(ConfluenceXWikiGeneratorListener.java:346)
at org.xwiki.rendering.wikimodel.impl.InternalWikiScannerContext.onMacroBlock(InternalWikiScannerContext.java:1065)
at org.xwiki.rendering.wikimodel.impl.WikiScannerContext.onMacroBlock(WikiScannerContext.java:602)
at org.xwiki.rendering.wikimodel.impl.WikiScannerContext.onMacro(WikiScannerContext.java:595)
at org.xwiki.contrib.confluence.parser.xhtml.internal.wikimodel.ConfluenceListItemTagHandler.end(ConfluenceListItemTagHandler.java:51)
at org.xwiki.rendering.wikimodel.xhtml.handler.TagHandler.endElement(TagHandler.java:85)
at org.xwiki.rendering.wikimodel.xhtml.impl.TagContext.endElement(TagContext.java:81)
at org.xwiki.rendering.wikimodel.xhtml.impl.TagStack.endElement(TagStack.java:123)
at org.xwiki.rendering.wikimodel.xhtml.impl.XhtmlHandler.endElement(XhtmlHandler.java:199)
at org.xml.sax.helpers.XMLFilterImpl.endElement(java.xml@25.0.3/XMLFilterImpl.java:558)
at org.xwiki.rendering.wikimodel.xhtml.filter.XHTMLWhitespaceXMLFilter.endElement(XHTMLWhitespaceXMLFilter.java:190)
at org.xwiki.contrib.confluence.parser.xhtml.internal.wikimodel.ConfluenceXHTMLWhitespaceXMLFilter.endElement(ConfluenceXHTMLWhitespaceXMLFilter.java:90)
at org.xml.sax.helpers.XMLFilterImpl.endElement(java.xml@25.0.3/XMLFilterImpl.java:558)
at org.xml.sax.helpers.XMLFilterImpl.endElement(java.xml@25.0.3/XMLFilterImpl.java:558)
at org.xwiki.rendering.wikimodel.xhtml.filter.AccumulationXMLFilter.endElement(AccumulationXMLFilter.java:86)
at org.xml.sax.helpers.XMLFilterImpl.endElement(java.xml@25.0.3/XMLFilterImpl.java:558)
at org.xwiki.rendering.wikimodel.xhtml.filter.DTDXMLFilter.endElement(DTDXMLFilter.java:86)
{code}
Note that this stacktrace stays the same for all dumps while the endless loop is running - it actually was exactly the same for multiple dumps, so I suspect this endless loop is rather tight.

 

{{ConfluenceWrappingListener}} looks suspicious in this context, as {{queueEvents}} and {{dequeueEvents}} look asymmetric:

{code:java}
    void queueEvents(Listener listener)
    {
        queuedListeners.push(listener);
        super.setWrappedListener(listener);
    }

    /**
     * Remove the given listener from the queue. When there are wrapping listeners around, rewire so that the removed
     * listener doesn't receive events anymore.
     * @param listenerToRemove the listener to remove
     */
    void dequeueEvents(Listener listenerToRemove)
    {
        Listener prev = null;
        Iterator<Listener> it = queuedListeners.iterator();
        while (it.hasNext()) {
            Listener cur = it.next();
            if (cur == listenerToRemove) {
                it.remove();
                if (prev instanceof WrappingListener) {
                    Listener next = it.hasNext() ? it.next() : compositeListener;
                    ((WrappingListener) prev).setWrappedListener(next);
                }
                break;
            } else if (cur instanceof RightContextAnnotationFilter) {
                /* FIXME: Calling stop for RightContextAnnotationFilter specifically doesn't feel completely right,
                     a more generic way of handling this would be nice.
                     This complexity mostly comes from the titles being replayed to generate their Confluence
                     anchors in ConfluenceConverterListener.
                */
                ((RightContextAnnotationFilter) cur).stop();
            }
            prev = cur;
        }
        if (queuedListeners.isEmpty()) {
            super.setWrappedListener(compositeListener);
        }
    }
{code}
 
Just by reading the loop I didn't really understand the relationship between {{
{} prev { }} } , {{ {} cur { }} } , {{next}} and the way {{prev}} is then updated if it is a {{ {} WrappingListener { }} } . Why does it then wrap {{ {} next { }} } , which is in the same queue as {{ {} prev { }} } , actually a direct sibling after {{cur}} was removed?

As far as I can currently tell, without having debugged it, is that {{ConfluenceConverterListener}} usually enqueues another {{
{} QueueListener { }} } , no {{ {} WrappingListeners { }} } , so I'm not sure when the {{instanceof}} will actually trigger. (Though I only looked at the code on GitHub so far, not with an IDE, and you can't easily follow the relationships in the code in the GitHub web UI.)

Also not sure if the following can happen: If {{listenerToRemove}} is the one currently wrapped by {{
{} this { }} } , but {{queuedListeners}} is not empty after the loop, it will not be replaced by the {{ {} compositeListener { }} } , and will stay wrapped even though it's not contained in {{queuedListeners}} any more.