Opened 14 years ago

Closed 12 years ago

#1294 closed defect (fixed)

custom output causes memory leak?

Reported by: ioly Owned by:
Priority: medium Milestone: 2.4
Component: Web API Version: 2.0.2
Severity: critical Keywords: custom output render memory leak
Cc: External ID:

Description

version 2.0.2, bundled tomcat. When using MgByteReader.Read to render custom output,
found memory leak. my code:

MySession session = ...
MyRenderOption option = ...
...
MgSelection selection = new MgSelection(map);
MgByteReader byteReader = session.getRenderingService().RenderMap(
                map,
                selection,
                envelope,
                option.ImageWidth,
                option.ImageHeight,
                new MgColor(option.R, option.G, option.B),
                "PNG"
);

byte[] tmp = new byte[1024 * 1024];
while (true) {
            int len = byteReader.Read(tmp, tmp.length);
            log(len);
            if (len <= 0){
                break;
            }
...

It worked and outputed the image correctly as expected, but the memory use
increased very fast and the jvm crashed after several calls.

Change History (5)

comment:1 by ioly, 14 years ago

Keywords: custom output render memory leak added

comment:2 by tomfukushima, 14 years ago

Does the same problem also occur in 2.1?

Please add a sample script and package that can be used to easily reproduce this problem.

comment:3 by ioly, 14 years ago

I've tried 2.1 but the problem still exists.

I use the bundled tomcat and the sample package "Sheboygan".

There are two jsp pages and one core class in my app: open_map.jsp, render.jsp, and class "MapService". "open_map.jsp" create a map guide session(and put everything in a global cache, site connection, map, session, rendering service and resource service), "render.jsp" use the former created session to generate custom output, and class "MapService" wraps the low-level operation.

When tomcat started up, I first visit the "open_map.jsp" to create a session, and then I visit "render.jsp" in the same browser window to get the output image, and then I refresh the page again and again. The output image is fine, but the memory use increases very fast.

open_map.jsp:

<%@page import="GIS.MapGuide.Data.Map"%>
<%@page import="GIS.MapGuide.Service.MapSession"%>
<%@ page import="GIS.MapGuide.Service.MapService"%>
<%@page contentType="text/plain;charset=gbk"%>
<%
    MapService service = new MapService();

    Map map = service.open("Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition");
    session.setAttribute("map", map);
    out.println(map.toString());
%>

render.jsp:

<%!
    double inchToMeter(double inch){
        return inch * .0254d;
    }

    static double _scale = 50000d;

    void _log(Object s){
        System.out.println(s);
    }
%>
<%
    try {
        _log("begin: " + System.currentTimeMillis());

         if (_scale < 1000){
            _scale = 50000;

        }else{
            _scale *= 0.9;
        }

        Map map = (Map)session.getAttribute("map");
        MapService service = new MapService();

		RenderOption option = new RenderOption();
		option.SessionId = map.getSessionid();
		option.Scale = _scale;
		option.CenterX = map.getViewCenter().getX();
		option.CenterY = map.getViewCenter().getY();
		option.ImageWidth = 600;
		option.ImageHeight = 600;

        byte[] buffer = service.render(option);
        _log("size: " + buffer.length);

        response.setHeader("Content-type", "image/png");
        response.getOutputStream().write(buffer, 0, buffer.length);

        _log("end: " + System.currentTimeMillis());

    }catch (Exception e) {
        e.printStackTrace();
    }
%>
<%@ page import="GIS.MapGuide.Data.Map" %>
<%@ page import="GIS.MapGuide.Data.RenderOption" %>
<%@ page import="GIS.MapGuide.Service.MapService" %>

MapService.java:

package GIS.MapGuide.Service;

import GIS.MapGuide.Data.Map;
import GIS.MapGuide.Data.RenderOption;
import org.osgeo.mapguide.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class MapService {
    private static String _config_file;

    public static void setConfigFilePath(String path) {
        _config_file = path;
        MapGuideJavaApi.MgInitializeWebTier(_config_file);
    }
    
    public Map open(String identifier) throws MgException {
        //login
        MgUserInformation user = new MgUserInformation("Administrator", "admin");
        MgSite site = new MgSite();
        site.Open(user);

        //create session
        String sessionId = site.CreateSession();

        MgSiteConnection con = new MgSiteConnection();
        con.Open(user);

        //get resource service
        MgResourceService resource_service = (MgResourceService)con.CreateService(MgServiceType.ResourceService);

        
        MgMap mm = new MgMap(con);
        MgResourceIdentifier id = new MgResourceIdentifier(identifier);
        _log(id.ToString());
        _log(id.GetName());
        mm.Create(resource_service, id, id.GetName());

        String sid = String.format("Session:%s//%s.%s", sessionId, mm.GetName(), MgResourceType.Map);
        _log(sid);
        mm.Save(resource_service, new MgResourceIdentifier(sid));
        _log(mm.GetResourceId().ToString());
        _log(mm.GetMapDefinition().ToString());

        //output
        MapSession session = new MapSession();
        MapSessionManager.put(mm.GetSessionId(), session);
        
        session.setMap(mm);
        session.setRenderingService((MgRenderingService)con.CreateService(MgServiceType.RenderingService));
        session.setResourceService(resource_service);
        session.setSessionId(sessionId);
        session.setSite(site);
        session.setSiteConnection(con);
        session.setUser(user);

        Map map = new Map();
        map.setMap(mm);
        _log(map);
        
        return map;
    }

    void _log(Object s){
        System.out.println(s);
    }

    double _inch_to_meter(double inch){
        return inch * .0254d;
    }

    public byte[] render(RenderOption option) throws MgException, IOException {
        MapSession session = MapSessionManager.get(option.SessionId);
        MgMap map = session.getMap();
        String srsWkt = map.GetMapSRS();
        MgCoordinateSystemFactory coordinateSystemFactory = new MgCoordinateSystemFactory();
        MgCoordinateSystem srs = coordinateSystemFactory.Create(srsWkt);

        double displayInInches = option.ImageHeight * 1d  / map.GetDisplayDpi();
        double displayInMeters = _inch_to_meter(displayInInches);
        double mapHeightInMeters = displayInMeters * option.Scale;
        double mapHeightInMapUnits = srs.ConvertMetersToCoordinateSystemUnits(mapHeightInMeters);
        double envelopeOffsetY = mapHeightInMapUnits / 2;
        double envelopeOffsetX = option.ImageWidth * 1d / option.ImageHeight * envelopeOffsetY;
        MgEnvelope envelope = new MgEnvelope(
                option.CenterX - envelopeOffsetX,
                option.CenterY - envelopeOffsetY,
                option.CenterX + envelopeOffsetX,
                option.CenterY + envelopeOffsetY
        );

        byte[] tmp = new byte[1024 * 1024];

        MgSelection selection = new MgSelection(map);
        MgByteReader byteReader = session.getRenderingService().RenderMap(
                map,
                selection,
                envelope,
                option.ImageWidth,
                option.ImageHeight,
                new MgColor(option.R, option.G, option.B),
                "PNG"
        );

        ByteArrayOutputStream buffer = new ByteArrayOutputStream();

        while (true) {
            int len = byteReader.Read(tmp, tmp.length);
            log(len);
            if (len <= 0){
                break;
            }
            if (len > 0) {
                buffer.write(tmp, 0, len);

            } else {
                break;
            }
        }

        byteReader.delete();
        tmp = buffer.toByteArray();
        buffer.close();
        return tmp;
    }

    void log(Object s){
        System.out.println("MapService: " + s);
    }
}

comment:4 by ioly, 14 years ago

I've found a way to avoid this problem. I notice the "MgByteReader.ToFile" method and I use it to avoid calling "MgByteReader.Read",then it seems working stably.

original code:

        byte[] tmp = new byte[1024 * 1024];
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        while (true) {
            int len = byteReader.Read(tmp, tmp.length);
            log(len);
            if (len > 0) {
                buffer.write(tmp, 0, len);

            } else {
                break;
            }
        }

new code:

        File file = File.createTempFile(session.getSessionId(), ".png");
        byteReader.ToFile(file.getAbsolutePath());

        FileChannel channel = new FileInputStream(file).getChannel();
        ByteBuffer buf = ByteBuffer.allocate((int)channel.size());
        while(buf.position() < buf.capacity()){
            channel.read(buf);
        }

        channel.close();
        tmp = buf.array();
        file.delete();
        buf.clear();

It's not graceful, but works.

comment:5 by jng, 12 years ago

Milestone: 2.4
Resolution: fixed
Status: newclosed

swig has been patched to correctly generate self-cleaning java proxy classes in r6744 in 2.4 branch and r6611 in trunk

Note: See TracTickets for help on using tickets.