[prev in list] [next in list] [prev in thread] [next in thread] 

List:       batik-dev
Subject:    DO NOT REPLY [Bug 41988]  New:  - DOMScrambler torture test causes heap space OutOfMemoryError
From:       bugzilla () apache ! org
Date:       2007-03-29 23:30:19
Message-ID: bug-41988-30099 () http ! issues ! apache ! org/bugzilla/
[Download RAW message or body]

DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUG·
RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT
<http://issues.apache.org/bugzilla/show_bug.cgi?id=41988>.
ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED AND·
INSERTED IN THE BUG DATABASE.

http://issues.apache.org/bugzilla/show_bug.cgi?id=41988

           Summary: DOMScrambler torture test causes heap space
                    OutOfMemoryError
           Product: Batik
           Version: 1.6
          Platform: PC
        OS/Version: Linux
            Status: NEW
          Severity: major
          Priority: P2
         Component: SVG DOM
        AssignedTo: batik-dev@xmlgraphics.apache.org
        ReportedBy: archie@dellroad.org


I've written a Batik torture test that loads an SVG document and makes zillions
of DOM changes. Running this test on some (but not all) SVG documents that I've
tried eventually leads to an OutOfMemoryException.

Therefore, there must be a memory leak in Batik somewhere relating to (or
triggered by) DOM manipulation.

Example of a document that triggers the memory leak:
 
http://svn.apache.org/repos/asf/xmlgraphics/batik/trunk/samples/tests/spec/linking/linkingViewBox.svg

Example of a document that doesn't seem to trigger the memory leak:
  http://www.living-pages.de/de/projects/xop/samples/tiger.svg

Here is the torture test and the little shell script I use to run it:

--------------------------------------------------------------------

import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import org.apache.batik.bridge.UpdateManager;
import org.apache.batik.bridge.UpdateManagerEvent;
import org.apache.batik.bridge.UpdateManagerListener;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.gvt.GVTTreeRendererListener;
import org.apache.batik.swing.svg.GVTTreeBuilderEvent;
import org.apache.batik.swing.svg.GVTTreeBuilderListener;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.swing.svg.SVGDocumentLoaderListener;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherEvent;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherListener;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.svg.SVGDefsElement;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGGElement;

public class DOMScrambler extends WindowAdapter implements
  SVGDocumentLoaderListener, GVTTreeBuilderListener,
  SVGLoadEventDispatcherListener, GVTTreeRendererListener,
  UpdateManagerListener {

    // Tweak these parameters to increase/decrease the torture
    public static final int SCRAMBLER_TIME = 100;           // lower = more torture
    public static final int SCRAMBLER_NUM_CHANGES = 10;     // higher = more torture

    private final Logger log = Logger.getLogger(getClass());
    private final String url;
    private final JFrame frame;
    private final JSVGCanvas canvas;
    private final Random random = new Random();
    private final ArrayList<SVGGElement> gnodes = new ArrayList<SVGGElement>();

    private SVGDocument dom;

    public abstract class RepeatingThread extends Thread {
        public void run() {
            try {
                while (true) {
                    execute();
                }
            } catch (Throwable t) {
                t.printStackTrace(System.err);
            }
        }
        protected abstract void execute();
        protected void delay(int millis) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public class MemoryReportingThread extends RepeatingThread {
        protected void execute() {
            Runtime runtime = Runtime.getRuntime();
            runtime.gc();
            System.out.println("MEMORY:");
            System.out.println("\tFREE:\t" + runtime.freeMemory());
            System.out.println("\tTOTAL:\t" + runtime.totalMemory());
            System.out.println("\tMAX:\t" + runtime.maxMemory());
            delay(10000);
        }
    }

    public class SleeperThread extends RepeatingThread {
        protected void execute() {
            delay(nextInt(500));
        }
    }

    public class SwingNoise extends SleeperThread {
        protected void execute() {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    delay(nextInt(20));
                }
            });
            super.execute();
        }
    }

    public class BatikNoise extends SleeperThread {
        protected void execute() {
            updateSVG(new Runnable() {
                public void run() {
                    delay(nextInt(20));
                }
            });
            super.execute();
        }
    }

    public class Scrambler extends SleeperThread {
        protected void execute() {
            updateSVG(new Runnable() {
                public void run()  {
                    for (int i = 0; i < nextInt(SCRAMBLER_NUM_CHANGES); i++)
                        scramble();
                }
            });
            delay(nextInt(SCRAMBLER_TIME));
        }
        protected void scramble() {
          tryagain:
            while (true) {
                SVGGElement g1 = gnodes.get(nextInt(gnodes.size()));
                SVGGElement g2 = gnodes.get(nextInt(gnodes.size()));
                NodeList kids1 = g1.getChildNodes();
                NodeList kids2 = g2.getChildNodes();
                if (kids1.getLength() == 0 && kids2.getLength() == 0)
                    continue;
                Element parent1 = kids1.getLength() > 0 ? g1 : g2;
                Element parent2 = parent1 == g1 ? g2 : g1;
                kids1 = parent1.getChildNodes();
                Node child = kids1.item(nextInt(kids1.getLength()));

                // Verify parent2 is not child or in child's subtree
                for (Node ancestor = parent2; ancestor != null;
                  ancestor = ancestor.getParentNode()) {
                    if (ancestor == child)
                        continue tryagain;
                }

                // Swizzle child node
                //log.debug("moving child " + child + " from " + parent1 + " to
" + parent2);
                parent1.removeChild(child);
                parent2.appendChild(child);
                break;
            }
        }
    }

    public DOMScrambler(String url) {
        this.url = url;
        frame = new JFrame("DOMScrambler: "
          + url.substring(url.lastIndexOf('/') + 1));
        canvas = new JSVGCanvas(null, true, true);
        canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
        canvas.addSVGDocumentLoaderListener(this);
        canvas.addGVTTreeBuilderListener(this);
        canvas.addGVTTreeRendererListener(this);
        canvas.addSVGLoadEventDispatcherListener(this);
        canvas.addUpdateManagerListener(this);
        canvas.setFocusable(true);
        frame.addWindowListener(this);
    }

    public void go() {
        canvas.setURI(this.url);
    }

    public void cancel() {
        System.err.println("Something went wrong");
    }

    protected void updateSVG(final Runnable rable) {
        UpdateManager updateManager = canvas.getUpdateManager();
        updateManager.getUpdateRunnableQueue().invokeLater(
          new Runnable() {
            public void run() {
                try {
                    rable.run();
                } catch (Throwable t) {
                    t.printStackTrace(System.err);
                    System.exit(1);
                }
            }
        });
    }

    protected int nextInt(int bound) {
        synchronized (random) {
            return random.nextInt(bound);
        }
    }

    protected void ready() {

        // Get SVG DOM
        this.dom = canvas.getSVGDocument();

        // Locate all <g> nodes
        findGNodes(this.dom.getDocumentElement());

        // Start threads
        new MemoryReportingThread().start();
        new SleeperThread().start();
        new SleeperThread().start();
        new SleeperThread().start();
        new SwingNoise().start();
        new SwingNoise().start();
        new BatikNoise().start();
        new BatikNoise().start();
        new Scrambler().start();
    }

    private void findGNodes(Element elem) {
        if (elem instanceof SVGDefsElement)
            return;
        NodeList nodeList = elem.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node item = nodeList.item(i);
            if (item instanceof SVGGElement)
                gnodes.add((SVGGElement)item);
            if (item instanceof Element)
                findGNodes((Element)item);
        }
    }

    public void showFrame() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                frame.getContentPane().add(canvas);
                frame.pack();
                Dimension size = frame.getSize();
                Point center = GraphicsEnvironment
                  .getLocalGraphicsEnvironment().getCenterPoint();
                int x = (int)(center.getX() - size.getWidth() / 2);
                int y = (int)(center.getY() - size.getHeight() / 2);
                frame.setLocation(new Point(x, y));
                frame.setVisible(true);
                frame.toFront();
            }
        });
    }

    // SVGDocumentLoaderListener methods

    public void documentLoadingStarted(SVGDocumentLoaderEvent e) {
        log.debug("Document loading started");
    }

    public void documentLoadingCompleted(SVGDocumentLoaderEvent e) {
        log.debug("Document loading completed");
    }

    public void documentLoadingCancelled(SVGDocumentLoaderEvent e) {
        log.debug("Document loading canceled");
    }

    public void documentLoadingFailed(SVGDocumentLoaderEvent e) {
        log.debug("Document loading failed: " + e);
        cancel();
    }

    // GVTTreeBuilderListener methods

    public void gvtBuildStarted(GVTTreeBuilderEvent e) {
        log.debug("GVT build started");
    }

    public void gvtBuildCompleted(GVTTreeBuilderEvent e) {
        log.debug("GVT build completed");
        showFrame();
    }

    public void gvtBuildCancelled(GVTTreeBuilderEvent e) {
        log.debug("GVT build canceled");
    }

    public void gvtBuildFailed(GVTTreeBuilderEvent e) {
        log.debug("GVT build failed: " + e);
        cancel();
    }

    // GVTTreeRendererListener methods

    public void gvtRenderingPrepare(GVTTreeRendererEvent e) {
        log.debug("GVT rendering preparing");
    }

    public void gvtRenderingStarted(GVTTreeRendererEvent e) {
        log.debug("GVT rendering started");
    }

    public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
        log.debug("GVT rendering complete");
        ready();
    }

    public void gvtRenderingCancelled(GVTTreeRendererEvent e) {
        log.debug("GVT rendering canceled");
    }

    public void gvtRenderingFailed(GVTTreeRendererEvent e) {
        log.debug("GVT rendering failed: " + e);
        cancel();
    }

    // SVGLoadEventDispatcherListener methods

    public void svgLoadEventDispatchCancelled(SVGLoadEventDispatcherEvent e) {
        log.debug("Load event dispatch cancelled");
    }

    public void svgLoadEventDispatchCompleted(SVGLoadEventDispatcherEvent e) {
        log.debug("Load event dispatch completed");
    }

    public void svgLoadEventDispatchFailed(SVGLoadEventDispatcherEvent e) {
        log.debug("Load event dispatch failed: " + e);
    }

    public void svgLoadEventDispatchStarted(SVGLoadEventDispatcherEvent e) {
        log.debug("Load event dispatch started");
    }

    // UpdateManagerListener

    public void managerStarted(UpdateManagerEvent e) {
        log.debug("Update manager started");
    }

    public void managerSuspended(UpdateManagerEvent e) {
        log.debug("Update manager suspended");
    }

    public void managerResumed(UpdateManagerEvent e) {
        log.debug("Update manager resumed");
    }

    public void managerStopped(UpdateManagerEvent e) {
        log.debug("Update manager stopped");
    }

    public void updateStarted(UpdateManagerEvent e) {
        //log.debug("Update manager started");
    }

    public void updateCompleted(UpdateManagerEvent e) {
        //log.debug("Update manager completed");
    }

    public void updateFailed(UpdateManagerEvent e) {
        log.debug("Update manager failed: " + e);
    }

    // WindowListener methods

    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }

    public static void main(String[] args) throws Exception {
        ConsoleAppender consoleAppender = new ConsoleAppender(
          new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN),
          ConsoleAppender.SYSTEM_ERR);
        consoleAppender.setThreshold(Level.DEBUG);
        BasicConfigurator.configure(consoleAppender);
        if (args.length != 1) {
            System.err.println("Usage: java DOMScrambler <file | URL>");
            System.exit(1);
        }
        File file = new File(args[0]);
        if (file.exists())
            args[0] = file.toURL().toString();
        new DOMScrambler(args[0]).go();
    }
}

--------------------------------------------------------------------

#!/bin/sh

LIBS=`build-classpath batik jakarta-commons-collections jakarta-commons-lang
jakarta-commons-logging log4j spring xerces-j2 xml-commons-apis`

set -e
mkdir -p classes
javac -d classes -classpath "${LIBS}" DOMScrambler.java
java -Xmx40m -classpath classes:"${LIBS}" DOMScrambler ${1+"$@"}

--------------------------------------------------------------------

-- 
Configure bugmail: http://issues.apache.org/bugzilla/userprefs.cgi?tab=email
------- You are receiving this mail because: -------
You are the assignee for the bug, or are watching the assignee.

---------------------------------------------------------------------
To unsubscribe, e-mail: batik-dev-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: batik-dev-help@xmlgraphics.apache.org

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic