What could be making Jasper Reports to throw java.

2019-08-04 04:59发布

问题:

I'm running a process on a test environment that takes more than 10 hours to run and generates PDF documents using Jasper Reports v3.7.5.

Quite frequently the process finishes successfully but in some cases the process fails throwing this exception:

20/05/2017 02:45:23.503 ERROR [process-pool-2-thread-20]  net.sf.jasperreports.extensions.DefaultExtensionsRegistry - Error instantiating extensions registry for simple.font.families
net.sf.jasperreports.engine.JRRuntimeException: java.io.IOException: Problem reading font data.
    at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:77)
    at net.sf.jasperreports.engine.fonts.SimpleFontFamily.createFontFace(SimpleFontFamily.java:316)
    at net.sf.jasperreports.engine.fonts.SimpleFontFamily.setNormal(SimpleFontFamily.java:85)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.parseFontFamily(SimpleFontExtensionHelper.java:233)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.parseFontFamilies(SimpleFontExtensionHelper.java:204)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.loadFontFamilies(SimpleFontExtensionHelper.java:173)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.loadFontFamilies(SimpleFontExtensionHelper.java:142)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory.createRegistry(SimpleFontExtensionsRegistryFactory.java:63)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.instantiateRegistry(DefaultExtensionsRegistry.java:238)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.loadRegistries(DefaultExtensionsRegistry.java:213)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.loadRegistries(DefaultExtensionsRegistry.java:162)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.getRegistries(DefaultExtensionsRegistry.java:132)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.getExtensions(DefaultExtensionsRegistry.java:104)
    at net.sf.jasperreports.engine.util.JRStyledTextParser.<clinit>(JRStyledTextParser.java:76)
    at net.sf.jasperreports.engine.fill.JRBaseFiller.<init>(JRBaseFiller.java:182)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:77)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:87)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:57)
    at net.sf.jasperreports.engine.fill.JRFiller.createFiller(JRFiller.java:142)
    at net.sf.jasperreports.engine.fill.JRFiller.fillReport(JRFiller.java:78)
    at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:624)
    at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:605)
    ...
Caused by: java.io.IOException: Problem reading font data.
    at java.awt.Font.createFont0(Font.java:1000)
    at java.awt.Font.createFont(Font.java:877)
    at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:69)
    ... 120 common frames omitted       

Soon after, on the same thread, the following error is logged. I'm not sure if it is related, though:

20/05/2017 02:45:23.605 ERROR [process-pool-2-thread-20]  my.package.MyClass.NoClassDefFoundError - AbstractReportCreationService.createAndPersistReport(...) threw an error: Could not initialize class sun.awt.X11GraphicsEnvironment

I have tried running this process on Solaris 5.10 and 5.11, using the same Java 1.8.0 version. It occurs randomly on both. I have been trying to reproduce the error in order to find the underlying cause, but to no avail so far.

I've read similar issues reported in StackOverflow and Jaspersoft Community forums. Most of these posts mention possibly permission issues preventing the process from reading from or writing to the java temp directory (java.io.tmpdir = /var/tmp/). See for example:

The method JasperFillManager.fillReport () throws java.io.Exception

I've checked file permissions and these are correct. When the process runs successfully it creates temporary font files called something like +~JF9070759829719582131.tmp or similar random names. Even when the process has failed it has left some of these temporary font files in the /var/tmp/ directory, so permissions doesn't seem to be the issue. On top of that, permissions are not changed between successful and failed runs.

On this post to Jaspersoft Community forum:

http://community.jaspersoft.com/questions/543492/javaioioexception-problem-reading-font-data

a proposed solution is to start Tomcat with option

-Djava.awt.headless=true

The process I'm running uses plain Java, not Tomcat, but I'm willing to try the headless mode. Since the problem occurs randomly and takes so long to run, it is going to be hard to prove that this possible solution actually resolves the issue. I'm worried about deploying this to the production environment and getting the same issue there randomly. Can anyone explain why running in headless mode might fix the issue, or what other possible solution should I try, please?

回答1:

We’ve resolved this exact same issue in our code.

It would occur randomly. We resolved that the actual issue was being swallowed by java and disguised under the Exception

Caused by: java.io.IOException: Problem reading font data.
                at java.awt.Font.createFont0(Font.java:1000)
                at java.awt.Font.createFont(Font.java:877)
                at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:69)
                ... 120 common frames omitted 

So I wrote a piece of code that would replicate all the functionality required to create a font but wouldn’t swallow the true exception. We ran the process in screen (server sided session we can attach & detach from) for 24 hours without any issues.

It wasn’t until we had the user that was initially launching the problem function, execute the new test function in a screen. The following error was produced -

java.io.IOException: Problem reading font data.
        at judson.Main.createFont0(Main.java:203)
        at judson.Main.createFont(Main.java:94)
        at judson.Main.createFont(Main.java:49)
        at judson.Main.main(Main.java:39)
Caused by: java.awt.AWTError: Can't connect to X11 window server using 'localhost:12.0' as the value of the DISPLAY variable.
        at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)

It turns out that the user was using a putty manager, which was automagically setting the DISPLAY variable.

/home/user/judsona/tmp/FontErrorDetector/bin : echo $DISPLAY
localhost:15.0

Further investigation into why it only failed occasionally lead to us to discover that when the session was available (X11 was available) it would work, but as soon as the session disconnected it would fail. Note: this would happen even if the initiating user was detaching from the screen session, it would wait until they closed their session after detaching before failing

So I investigated GraphicsEnvironment code & X11GraphicsEnvironment

X11GraphicsEnvironment.java

static {
    java.security.AccessController.doPrivileged(
                      new java.security.PrivilegedAction() {
        public Object run() {
            System.loadLibrary("awt");

            /*
             * Note: The MToolkit object depends on the static initializer
             * of X11GraphicsEnvironment to initialize the connection to
             * the X11 server.
             */
            if (!isHeadless()) {
                // first check the OGL system property
                boolean glxRequested = false;
                String prop = System.getProperty("sun.java2d.opengl");
                if (prop != null) {
                    if (prop.equals("true") || prop.equals("t")) {
                        glxRequested = true;
                    } else if (prop.equals("True") || prop.equals("T")) {
                        glxRequested = true;
                        glxVerbose = true;
                    }
                }

                // Now check for XRender system property
                boolean xRenderRequested = true;
                boolean xRenderIgnoreLinuxVersion = false;
                String xProp = System.getProperty("sun.java2d.xrender");
                    if (xProp != null) {
                    if (xProp.equals("false") || xProp.equals("f")) {
                        xRenderRequested = false;
                    } else if (xProp.equals("True") || xProp.equals("T")) {
                        xRenderRequested = true;
                        xRenderVerbose = true;
                    }

                    if(xProp.equalsIgnoreCase("t") || xProp.equalsIgnoreCase("true")) {
                        xRenderIgnoreLinuxVersion = true;
                    }
                }

                // initialize the X11 display connection
                initDisplay(glxRequested); 

^ it would initDisplay when it has determined it isn’t headless.

GraphicsEnvironment.java

 /**
 * @return the value of the property "java.awt.headless"
 * @since 1.4
 */
private static boolean getHeadlessProperty() {
    if (headless == null) {
        java.security.AccessController.doPrivileged(
        new java.security.PrivilegedAction<Object>() {
            public Object run() {
                String nm = System.getProperty("java.awt.headless");

                if (nm == null) {
                    /* No need to ask for DISPLAY when run in a browser */
                    if (System.getProperty("javaplugin.version") != null) {
                        headless = defaultHeadless = Boolean.FALSE;
                    } else {
                        String osName = System.getProperty("os.name");
                        if (osName.contains("OS X") && "sun.awt.HToolkit".equals(
                                System.getProperty("awt.toolkit")))
                        {
                            headless = defaultHeadless = Boolean.TRUE;
                        } else {
                            headless = defaultHeadless =
                                Boolean.valueOf(("Linux".equals(osName) ||
                                                 "SunOS".equals(osName) ||
                                                 "FreeBSD".equals(osName) ||
                                                 "NetBSD".equals(osName) ||
                                                 "OpenBSD".equals(osName)) &&
                                                 (System.getenv("DISPLAY") == null));
                        }
                    }
               } else if (nm.equals("true")) {
                    headless = Boolean.TRUE;
                } else {
                    headless = Boolean.FALSE;
                }
                return null;
            }
            }
        );
    }
    return headless.booleanValue();
}

When DISPLAY variable is set java determines that it is NOT headless, which means it will try to establish connection to X11 Server (which will be disconnected when the user closes their session causing GraphicsEnvironment to fail to start)

Conclusion –

You have three options

  1. Start your application with the -Djava.awt.headless=true
  2. Start your application with DISPLAY unset.
  3. Make sure no-one uses a putty manager that automagically sets X11 DISPLAY variable.

Personally I would use #1 -Djava.awt.headless=true