Memory Leak
See original GitHub issueRead 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:
- Created 3 years ago
- Reactions:4
- Comments:17 (8 by maintainers)
Top GitHub Comments
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”); } }`
@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 shotsscreen shot of file size and memory footprint