question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Read and complete the full issue template

Do not randomly delete sections. They are here for a reason.

Do you want to request a feature or report a bug?

  • Bug
  • Feature
  • Question

Did you test against the latest CI build?

  • Yes
  • No

If you answered No, please test with the latest development build first.

Version of ClosedXML 0.95.4

What is the current behavior? Memory Leak: Every time I export an XLWorkbook, it leaves some amount of memory in there EVEN AFTER DISPOSING the XLWorkbook object. Workaround is to use GC.Collect, which is not feasible because GC.Collect is a blocker for other threads. After only 3 downloads of 6 MB excel file, memory usage spikes more than 3 GB. If I use GC.Collect, it stays at around 1 GB. The object that’s not being disposed is: Dictionary<String, Int32> [Static variable ClosedXML.Excel.XLHelper.letterIndexes] This is happening for all the versions from 0.90.0 to 0.95.4

What is the expected behavior or new feature? It should dispose all the unused objects clear memory usage when I dispose XLWorkbook.

Is this a regression from the previous version? Yes

Regressions get higher priority. Test against the latest build of the previous minor version. For example, if you experience a problem on v0.95.3, check whether it the problem occurred in v0.94.2 too.

Reproducibility

This is an important section. Read it carefully. Failure to do so will cause a ‘RTFM’ comment. I have created a sample C# .net core web api project from the default template provided by Visual Studio 2019 and all the changes I made and sample file I used are mentioned below. Let me know if you want me to provide a sample code’s zip file or you want me to upload to my github account. Excel file is attached as well. Please let me know if you need any further information to reproduce. It should be fairly straightforward to reproduce. Just run the controller 3 times (http://localhost:62975/api/values/report) and you will see memory spiking up with every download.

Code to reproduce problem:

public class ExcelFormatter : OutputFormatter
    {
        /// <summary>
        /// Default constructor for ExcelFormatter
        /// </summary>
        public ExcelFormatter()
        {
            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
        }

        /// <summary>
        /// Specifies if the formatter can write to the indicated type.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        protected override bool CanWriteType(Type type)
        {
            return type == typeof(DataTable);
        }

        /// <summary>
        /// This handles the serialization of the data to Excel.
        /// </summary>
        /// <param name="context">The current context which includes the object to return and the response object</param>
        public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
        {
            try
            {
                DataTable sourceDataTable = context.Object as DataTable;

                string reportName = "ExcelReport";
                sourceDataTable.TableName = "Results";

                using (XLWorkbook wb = new XLWorkbook())
                {
                    wb.Worksheets.Add(sourceDataTable);

                    context.HttpContext.Response.Headers.Add("content-disposition", "attachment;filename=" + reportName + ".xlsx");
                    wb.SaveAs(context.HttpContext.Response.Body);
                    wb.Dispose();
                }

                return Task.FromResult(0);
            }
            catch(Exception ex)
            {
                throw ex;
            }
            finally
            {
                //GC.Collect();
            }
        }
    }


[Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody] string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody] string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }

        
        [HttpGet("report"), Produces("application/json", new[] { "text/xml", "text/csv", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, Type = typeof(DataTable))]        
        public async Task<IActionResult> report()
        {
            using (DataTable dt = await generateTable())
            {
                return Ok(dt);
            }
        }

        private async Task<DataTable> generateTable()
        {
            string filepath = @"C:\Users\sthakkar\Downloads\report.xlsx";
            string sqlquery = "Select * From [Results$]";
            
            string constring = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + filepath + ";Extended Properties=\"Excel 12.0;HDR=YES;\"";
            using (OleDbConnection con = new OleDbConnection(constring + ""))
            {
                using (OleDbDataAdapter da = new OleDbDataAdapter(sqlquery, con))
                {
                    using (DataSet ds = new DataSet())
                    {
                        da.Fill(ds);
                        return ds.Tables[0];
                    }
                }
            }                
        }
    }


public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                options.OutputFormatters.Insert(3, new ExcelFormatter());
            }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }
[report.xlsx](https://github.com/ClosedXML/ClosedXML/files/6085347/report.xlsx)

  • I attached a sample spreadsheet. (You can drag files on to this issue)

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:4
  • Comments:17 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
brunotourinhocommented, Feb 17, 2023

I understand it’s not a leak. I’m just looking for some way to release the memory back, after file download. Here is my code shortened for brevity… it’s a web application, the workbook has about 33K lines, and takes ~870Mb in memory, that is never released. https://imgur.com/a/fWcV7jD

`public async Task<IActionResult> Export() { using (var workbook = new XLWorkbook()) { var ws = workbook.Worksheets.Add(“MyAwesomeData”); ws.Cell(“A1”).InsertData(…);

GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();

return workbook.Deliver(“MyAwesomeFileName”); } }`

2reactions
jwilburncommented, Nov 15, 2021

@igitur , @vbjay not sure if anyone has submitted a MVCE yet, so here is one.

.NET Core 6.0 console app

this is still a problem. using ClosedXML 0.95.4. I get terrible performance and memory problems generating an excel file with 10 columns and 100,000 rows. Here is the code and screen shots

using (var workBook = new XLWorkbook())
{
    const int rowsToGenerate = 100_000;
    var workSheet = workBook.Worksheets.Add("Sample Data");
    var letters = "ABCDEFGHIJ".ToCharArray();

    // create 10 column headers
    foreach (var letter in letters)
        workSheet.Cell($"{letter}1").Value = $"Header{letter}";

    for (var rowIndex = 2; rowIndex < rowsToGenerate; rowIndex++)
    {
        for (var columnIndex = 0; columnIndex < 10; columnIndex++)
        {
            var headerName = letters[columnIndex];
            workSheet.Cell($"{headerName}{rowIndex}").Value = $"{headerName}{rowIndex}_data";
        }
    }

    var range = workSheet.RangeUsed();
    var table = range.CreateTable();
    table.Theme = XLTableTheme.TableStyleLight1;
    workSheet.Columns().AdjustToContents();
    var dir = @"C:\temp\ClosedXmlExample";
    if (!Directory.Exists(dir))
    {
        Directory.CreateDirectory(dir);
    }

    workBook.SaveAs(@$"{dir}\ClosedXmlExample.xlsx");
}

Console.ReadKey();

screen shot of file size and memory footprint image

Read more comments on GitHub >

github_iconTop Results From Across the Web

Memory leak
Memory leaks are a common error in programming, especially when using languages that have no built in automatic garbage collection, such as C...
Read more >
What is Memory Leak? How can we avoid?
Memory leak occurs when programmers create a memory in a heap and forget to delete it. The consequence of the memory leak is...
Read more >
Memory Leaks and Garbage Collection
Memory leaks are a form of computer brain drain that can cause systems to be unstable and unpredictable. The fix is called garbage...
Read more >
What Is a Memory Leak and How Do They Happen?
A memory leak is a portion of an application that uses memory from RAM without finally freeing it. The result is that an...
Read more >
Memory leak
Description. A memory leak is an unintentional form of memory consumption whereby the developer fails to free an allocated block of memory when...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found