RadChart Memory Leak

Thread is closed for posting
4 posts, 0 answers
  1. John
    John avatar
    44 posts
    Member since:
    Apr 2011

    Posted 26 Oct 2012 Link to this post

    I am having a similar problem, and I generated a short example to illustrate the problem. We are using a timer, but it happens to be a bit longer in cycle than the one supplied here. It just takes longer to run out of memory if the timer is longer. Note that the more items that are added to the chart, the larger the memory leak.

    I also took the liberty of looking through the Telerik source code and discovered that RadChart contains 2 Chart objects, one directly, and one indirectly through MapAreaBuilder. RadChart does not implement Dispose, so these items hang around much longer than they should. MapAreaBuilder also does not implement dispose, and it should as well. The basic idea is that if a class includes an object that implements IDisposable, the containing class must also implement IDisposable and dispose of the Disposable objects it contains. Yes, RadChart does inherit from Control that implements IDispose, but Control does not know to dispose of the 2 Charts that are contained in the RadChart object. In other words, RadChart needs to override Dispose(bool) and dispose of the Chart and the MapAreaBuilder if the parameter passed in is true. MapAreaBuilder needs to derive from IDisposable, implement Dispose(), Dispose(bool) and ~MapAreaBuilder().

    Default.aspx.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using Telerik.Web.UI;
    namespace RadChartLeakTest
    {
    public partial class _Default : System.Web.UI.Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    }
    protected void Timer1Tick(object sender, EventArgs e)
    {
    }
    }
    }

    Default.aspx
    <%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="RadChartLeakTest._Default" %>
    <%@ Register TagPrefix="telerik" Namespace="Telerik.Web.UI" Assembly="Telerik.Web.UI" %>
    <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    </asp:Content>
    <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <telerik:RadScriptManager ID="RadScriptManager1" runat="server">
    </telerik:RadScriptManager>
    <telerik:RadScriptBlock ID="Script1" runat="server">
    <script type="text/javascript">
    </script>
    </telerik:RadScriptBlock>
    <asp:Panel ID="PanelUpdateThis" runat="server">
    <telerik:RadChart runat="server" ID="Chart1" >
    <Appearance Border-Visible="false" FillStyle-FillType="Solid" FillStyle-MainColor="#f2f2f2">
    </Appearance>
    <ChartTitle Visible="false">
    </ChartTitle>
    <Legend Appearance-Border-Visible="false" Appearance-FillStyle-FillType="Solid" Appearance-FillStyle-MainColor="#f2f2f2">
    </Legend>
    <PlotArea Appearance-Border-Color="#f2f2f2" Appearance-FillStyle-MainColor="#f2f2f2"
    Appearance-FillStyle-FillType="Solid" EmptySeriesMessage-TextBlock-Text="No Sessions Last 24 Hours"
    EmptySeriesMessage-TextBlock-Appearance-TextProperties-Color="Black">
    <XAxis AutoScale="false" IsZeroBased="true">
    </XAxis>
    </PlotArea>
    <Series>
    </Series>
    </telerik:RadChart>
    <asp:Timer ID="Timer1" runat="server" Interval="1000" OnTick="Timer1Tick">
    </asp:Timer>
    </asp:Panel>
    </asp:Content>


  2. Yavor
    Admin
    Yavor avatar
    401 posts

    Posted 31 Oct 2012 Link to this post

    Hi John,

    Thank you for the in-depth report. I sent it yo our developers and they managed to track down and fix the leaks you reported. They will be included in the next major release.

    Greetings,
    Yavor
    the Telerik team
    If you want to get updates on new releases, tips and tricks and sneak peeks at our product labs directly from the developers working on the RadControls for ASP.NET AJAX, subscribe to their blog feed now.
  3. UI for ASP.NET Ajax is Ready for VS 2017
  4. John
    John avatar
    44 posts
    Member since:
    Apr 2011

    Posted 07 Oct 2014 in reply to Yavor Link to this post

    Was this ever fixed and released?  I still have the workaround code in place, and we would like to remove it.
  5. Danail Vasilev
    Admin
    Danail Vasilev avatar
    1490 posts

    Posted 08 Oct 2014 Link to this post

    Hello John,

    In the Telerik UI release notes (i.e., Q3 2012 SP1 and Q3 2012 SP2) posted after the date of my colleague's answer (i.e., 31-Oct-2012) there doesn't seem to figure such an item out.

    This, however, is an omission in the release notes because the RadChart's code consists of "Fix: Memory leak in RadChart" change set checked-in on 11/01/2012 and it looks like this:
    RadChart.cs:
    ...
            public override void Dispose()
            {
                if (this._chart != null)
                {
                    this._chart.Dispose();
                }
                if (this._mapAreaBuilder != null)
                {
                    this._mapAreaBuilder = null;
                }
                base.Dispose();
            }
    ...

    Chart.cs:
    internal void CalculateChart()
    {
        int width = (int)this.Appearance.Dimensions.Width.PixelValue;
        int height = (int)this.Appearance.Dimensions.Height.PixelValue;
        if (width > 0 && height > 0)
        {
            using (RenderEngine renderEngine = new RenderEngine(this, width, height))
            {
                Legend.BindSeriesToLegend(renderEngine);
            }
        }
    }
     
    /// <summary>
    /// Chart recalculation
    /// </summary>
    internal void ReCalculateChart()
    {
        int width = (int)this.Appearance.Dimensions.Width.PixelValue;
        int height = (int)this.Appearance.Dimensions.Height.PixelValue;
        if (width > 0 && height > 0)
        {
            using (RenderEngine renderEngine = new RenderEngine(this, width, height))
            {
                renderEngine.InitializeChartElements();
                renderEngine.CalculateElementsForRender();
            }
        }
    }


    internal Image GetStaticArea(int width, int height, bool withXAxis, bool withYAxis, bool withYAxis2)
    {
        this.Skin = this.Skin;
        if (width > 0 && height > 0)
        {
            RenderEngine renderEngine = new RenderEngine(this, width, height);
            try
            {
                CheckLimitations();
                renderEngine.InitializeChartElements();
                OnBeforeLayout(this, EventArgs.Empty);
                chartPlotArea.PrepareForScale();
                renderEngine.CalculateElementsForRender();
                OnPrePaint(this, null);
                return renderEngine.RenderChartArea(true, true, true, true, false, withXAxis, withYAxis, withYAxis2);
            }
            catch (ChartException ex)
            {
                return GetException(renderEngine, ex);
            }
            catch (Exception ex)
            {
                return GetException(renderEngine, new ChartException(DefaultValues.EXCEPTION_MESSAGE, ex));
            }
            finally
            {
                renderEngine.Dispose();
                renderEngine = null;
            }
        }
     
        return null;
    }

    internal Image GetPlotArea(int width, int height, float xScale, float yScale, Unit clientWidth, Unit clientHeight, Unit top, Unit left)
    {
        this.Skin = this.Skin;
     
        if (width > 0 && height > 0)
        {
            // Chart elements calculation after the post back
            ReCalculateChart();
     
            float scaledImageWidth = GetScaledImageWidth(xScale, yScale);
            float scaledImageHeight = GetScaledImageHeight(xScale, yScale);
     
            RenderEngine renderEngine = new RenderEngine(this, (int)Math.Round(scaledImageWidth), (int)Math.Round(scaledImageHeight), false);
            renderEngine.InitGraphics(width, height); // init ChartGraphics object with unscaled image size
            try
            {
                ChartPlotArea plotArea = chartPlotArea;
                PrepareForScale(xScale, yScale);
                renderEngine.InitializeChartElements();
                renderEngine.ScalePlotArea(xScale, yScale);
     
                float plotLeft = (float)Math.Round(plotArea.Appearance.Position.X);
                float plotTop = (float)Math.Round(plotArea.Appearance.Position.Y);
                int plotWidth = (int)Math.Round(clientWidth.PixelValue);
                int plotHeight = (int)Math.Round(clientHeight.PixelValue);
     
                if (SeriesOrientation == ChartSeriesOrientation.Vertical)
                {
                    plotLeft += plotArea.YAxis.Appearance.Width;
                }
                else
                {
                    plotLeft += plotArea.XAxis.Appearance.Width;
     
                    if (plotArea.YAxis2.IsVisible())
                        plotTop += plotArea.YAxis2.Appearance.Width;
                }
     
                renderEngine.InitGraphics(plotWidth, plotHeight);
                renderEngine.graphics.TranslateTransformDefault(-plotLeft - left.PixelValue, -plotTop - top.PixelValue);
     
                Image img = renderEngine.RenderPlotArea(true);
     
                RestoreAfterScale(width, height);
     
                return img;
            }
            catch (ChartException ex)
            {
                return GetException(renderEngine, ex);
            }
            catch (Exception ex)
            {
                return GetException(renderEngine, new ChartException(DefaultValues.EXCEPTION_MESSAGE, ex));
            }
            finally
            {
                renderEngine.Dispose();
                renderEngine = null;
            }
        }
     
        return null;
    }

    internal Image GetAxis(int width, int height, float xScale, float yScale, ChartAxisType axisType)
    {
        this.Skin = this.Skin;
     
        if (width > 0 && height > 0)
        {
            float w = width;
            float h = height;
     
            if (axisType == ChartAxisType.XAxis)
                yScale = 1;
            else
                xScale = 1;
     
            bool isChartVertical = this.SeriesOrientation == ChartSeriesOrientation.Vertical;
     
            if ((axisType == ChartAxisType.XAxis && isChartVertical) || (axisType != ChartAxisType.XAxis && !isChartVertical))
            {
                w = GetScaledImageWidth(xScale, yScale);
            }
            else
            {
                h = GetScaledImageHeight(xScale, yScale);
            }
     
            if (w > 0 && h > 0)
            {
                RenderEngine renderEngine = new RenderEngine(this, w, h, true);
                ChartPlotArea plotArea = chartPlotArea;
     
                try
                {
                    PrepareForScale(xScale, yScale);
                    renderEngine.InitializeChartElements();
                    renderEngine.ScalePlotArea(xScale, yScale);
     
                    RectangleF axisRect;
                    ChartAxis axis = null;
     
                    switch (axisType)
                    {
                        case ChartAxisType.XAxis:
                            axis = plotArea.XAxis;
                            break;
                        case ChartAxisType.YAxis:
                            axis = plotArea.YAxis;
                            break;
                        case ChartAxisType.YAxis2:
                            axis = plotArea.YAxis2;
                            break;
                        default:
                            break;
                    }
     
                    Image img = null;
                    if (axis != null)
                    {
                        axisRect = axis.GetClientRectangle();
     
                        if (axisRect.Width > 0 && axisRect.Height > 0)
                        {
                            renderEngine.InitGraphics((int)Math.Round(axisRect.Width), (int)Math.Round(axisRect.Height));
                            renderEngine.graphics.TranslateTransformDefault(-(int)Math.Round(axisRect.X), -(int)Math.Round(axisRect.Y));
                            img = renderEngine.RenderAxis(true, axisType);
                        }
                    }
     
                    RestoreAfterScale(width, height);
     
                    return img;
                }
                catch (ChartException ex)
                {
                    return GetException(renderEngine, ex);
                }
                catch (Exception ex)
                {
                    return GetException(renderEngine, new ChartException(DefaultValues.EXCEPTION_MESSAGE, ex));
                }
                finally
                {
                    renderEngine.Dispose();
                    renderEngine = null;
                }
            }
        }
     
        return null;
    }

    I can also suggest that you try the newer RadHtmlChart control that renders entirely on the client instead of the obsolete RadChart.


    Regards,
    Danail Vasilev
    Telerik
     

    Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

     
Back to Top