]> source.dussan.org Git - poi.git/commitdiff
Bugzilla 53780: Fixed memory and temporary file leak in SXSSF
authorYegor Kozlov <yegor@apache.org>
Fri, 14 Sep 2012 13:45:09 +0000 (13:45 +0000)
committerYegor Kozlov <yegor@apache.org>
Fri, 14 Sep 2012 13:45:09 +0000 (13:45 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1384784 13f79535-47bb-0310-9956-ffa450edef68

12 files changed:
src/documentation/content/xdocs/spreadsheet/how-to.xml
src/documentation/content/xdocs/status.xml
src/ooxml/java/org/apache/poi/xssf/streaming/GZIPSheetDataWriter.java
src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java
src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java
src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java
src/ooxml/testcases/org/apache/poi/xssf/SXSSFITestDataProvider.java
src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFCell.java
src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFHyperlink.java
src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFRow.java
src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFSheet.java
src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFWorkbook.java

index f51cf7372b50a2f73a739caf4bd8e67a8b885332..bb76a6b8073d80bbdb15bfd9ca543840d359c77a 100644 (file)
@@ -626,10 +626,6 @@ public class ExampleEventUserModel {
      </section>
      <anchor id="sxssf"/>
      <section><title>SXSSF (Streaming Usermodel API)</title>
-    <note>
-      SXSSF is a brand new contribution and some features were added after it was first introduced in POI 3.8-beta3.
-      Users are advised to try the latest build from trunk. Instructions how to build are <link href="../howtobuild.html">here</link>.
-    </note>
     <p>
     SXSSF (package: org.apache.poi.xssf.streaming)  is an API-compatible streaming extension of XSSF to be used when
     very large spreadsheets have to be produced, and heap space is limited.
@@ -656,6 +652,9 @@ public class ExampleEventUserModel {
     records that have not been flushed by a call to flushRows() are available
     for random access. 
     </p>
+   <p> 
+   Note that SXSSF allocates temporary files that you <strong>must</strong> always clean up explicitly, by calling the dispose method.
+   </p> 
     <p> The example below writes a sheet with a window of 100 rows. When the row count reaches 101, 
         the row with rownum=0 is flushed to disk and removed from memory, when rownum reaches 102 then the row with rownum=1 is flushed, etc.
     </p> 
@@ -672,7 +671,7 @@ import org.apache.poi.ss.util.CellReference;
 import org.apache.poi.xssf.streaming.SXSSFWorkbook;
 
     public static void main(String[] args) throws Throwable {
-        Workbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
+        SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
         Sheet sh = wb.createSheet();
         for(int rownum = 0; rownum < 1000; rownum++){
             Row row = sh.createRow(rownum);
@@ -697,6 +696,9 @@ import org.apache.poi.xssf.streaming.SXSSFWorkbook;
         FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
         wb.write(out);
         out.close();
+
+        // dispose of temporary files backing this workbook on disk
+        wb.dispose();
     }
 
 
@@ -712,7 +714,7 @@ import org.apache.poi.ss.util.CellReference;
 import org.apache.poi.xssf.streaming.SXSSFWorkbook;
 
     public static void main(String[] args) throws Throwable {
-        Workbook wb = new SXSSFWorkbook(-1); // turn off auto-flushing and accumulate all rows in memory
+        SXSSFWorkbook wb = new SXSSFWorkbook(-1); // turn off auto-flushing and accumulate all rows in memory
         Sheet sh = wb.createSheet();
         for(int rownum = 0; rownum < 1000; rownum++){
             Row row = sh.createRow(rownum);
@@ -735,7 +737,10 @@ import org.apache.poi.xssf.streaming.SXSSFWorkbook;
         FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
         wb.write(out);
         out.close();
-    }
+
+        // dispose of temporary files backing this workbook on disk
+        wb.dispose();
+   }
 
 
 ]]></source>
@@ -748,7 +753,6 @@ If the size of the temp files is an issue, you can tell SXSSF to use gzip compre
   wb.setCompressTempFiles(true); // temp files will be gzipped
 
 ]]></source>
-
      </section>
 
      <anchor id="low_level_api" />
index 5ea601b4c137fc1af20c464f2128073033dd4706..8c9595aa4fb44485ac60d776041a76497a119686 100644 (file)
@@ -34,6 +34,7 @@
 
     <changes>
         <release version="3.9-beta1" date="2012-??-??">
+          <action dev="poi-developers" type="fix">53780 - Fixed memory and temporary file leak in SXSSF </action>
           <action dev="poi-developers" type="fix">53380 - ArrayIndexOutOfBounds Excetion parsing word 97 document. </action>
           <action dev="poi-developers" type="fix">53434 - Subtotal is not return correct value. </action>
           <action dev="poi-developers" type="fix">53642 - [PATCH] XLS formula evaluation logging </action>
index 509c9df91827208f0047282527ab5a6085fd48d2..2a1a4a80b8f7fe08cb1240d0830bcca21777b189 100644 (file)
@@ -37,7 +37,6 @@ public class GZIPSheetDataWriter extends SheetDataWriter {
      */\r
     public File createTempFile()throws IOException {\r
         File fd = File.createTempFile("poi-sxssf-sheet-xml", ".gz");\r
-        fd.deleteOnExit();\r
         return fd;\r
     }\r
 \r
index a5a731b17c960516be47dbc9ef77ce6d4b4b755d..c2c438d7d46a5bde799b71bf72f6b73de4e2d9d8 100644 (file)
@@ -1364,4 +1364,12 @@ public class SXSSFSheet implements Sheet, Cloneable
         }
         return -1;
     }
+
+    /**
+     * Deletes the temporary file that backed this sheet on disk.
+     * @return true if the file was deleted, false if it wasn't.
+     */
+    boolean dispose() {
+        return _writer.dispose();
+    }
 }
index 73c4b934cf0d1156585d5e107b91424c6ef80572..207eef1224ef6f64c33f3d28ffe05e4812af77ed 100644 (file)
@@ -760,14 +760,40 @@ public class SXSSFWorkbook implements Workbook
        
         //Save the template
         File tmplFile = File.createTempFile("poi-sxssf-template", ".xlsx");
-        tmplFile.deleteOnExit();
-        FileOutputStream os = new FileOutputStream(tmplFile);
-        _wb.write(os);
-        os.close();
-
-        //Substitute the template entries with the generated sheet data files
-        injectData(tmplFile, stream);
-        tmplFile.delete();
+        try
+        {
+            FileOutputStream os = new FileOutputStream(tmplFile);
+            try
+            {
+                _wb.write(os);
+            }
+            finally
+            {
+                os.close();
+            }
+
+            //Substitute the template entries with the generated sheet data files
+            injectData(tmplFile, stream);
+        }
+        finally
+        {
+            tmplFile.delete();
+        }
+    }
+    
+    /**
+     * Dispose of temporary files backing this workbook on disk.
+     * Calling this method will render the workbook unusable.
+     * @return true if all temporary files were deleted successfully.
+     */
+    public boolean dispose()
+    {
+        boolean success = true;
+        for (SXSSFSheet sheet : _sxFromXHash.keySet())
+        {
+            success = sheet.dispose() && success;
+        }
+        return success;
     }
 
     /**
index f6c98317f54a2dfffd42747886b0ec68cea78d24..20a1d317bd79e606ccd7e894755a4a77f896cf27 100644 (file)
@@ -57,7 +57,6 @@ public class SheetDataWriter {
      */\r
     public File createTempFile()throws IOException {\r
         File fd = File.createTempFile("poi-sxssf-sheet", ".xml");\r
-        fd.deleteOnExit();\r
         return fd;\r
     }\r
 \r
@@ -302,4 +301,17 @@ public class SheetDataWriter {
             _out.write(chars, last, length - last);\r
         }\r
     }\r
+\r
+    /**\r
+     * Deletes the temporary file that backed this sheet on disk.\r
+     * @return true if the file was deleted, false if it wasn't.\r
+     */\r
+    boolean dispose() {\r
+        try {\r
+            _out.close();\r
+            return _fd.delete();\r
+        } catch (IOException e){\r
+            return false;\r
+        }\r
+    }\r
 }\r
index d5d7026b7a1e47355c1fa210361c327cd1722d57..a8ca27fdeffab223ed9f34420d36a623f63b9825 100644 (file)
@@ -30,6 +30,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 
 /**
  * @author Yegor Kozlov
@@ -37,12 +38,16 @@ import java.io.InputStream;
 public final class SXSSFITestDataProvider implements ITestDataProvider {
     public static final SXSSFITestDataProvider instance = new SXSSFITestDataProvider();
 
+    private ArrayList<SXSSFWorkbook> instances = new ArrayList<SXSSFWorkbook>();
+
     private SXSSFITestDataProvider() {
         // enforce singleton
     }
     public Workbook openSampleWorkbook(String sampleFileName) {
        XSSFWorkbook xssfWorkbook = XSSFITestDataProvider.instance.openSampleWorkbook(sampleFileName);
-       return new SXSSFWorkbook(xssfWorkbook);
+        SXSSFWorkbook swb = new SXSSFWorkbook(xssfWorkbook);
+        instances.add(swb);
+       return swb;
     }
 
     public Workbook writeOutAndReadBack(Workbook wb) {
@@ -62,7 +67,9 @@ public final class SXSSFITestDataProvider implements ITestDataProvider {
         return result;
     }
     public SXSSFWorkbook createWorkbook(){
-        return new SXSSFWorkbook();
+        SXSSFWorkbook wb = new SXSSFWorkbook();
+        instances.add(wb);
+        return wb;
     }
     public byte[] getTestDataFileContent(String fileName) {
         return POIDataSamples.getSpreadSheetInstance().readFile(fileName);
@@ -73,4 +80,14 @@ public final class SXSSFITestDataProvider implements ITestDataProvider {
     public String getStandardFileNameExtension() {
         return "xlsx";
     }
+
+    public synchronized boolean cleanup(){
+        boolean ok = true;
+        for(int i = 0; i < instances.size(); i++){
+            SXSSFWorkbook wb = instances.get(i);
+            ok = ok && wb.dispose();
+            instances.remove(i);
+        }
+        return ok;
+    }
 }
index 638dbe20d10110d13733ff0c857b72816a87686b..a0668754c5bab9d5c23a60a1acc3e4d17ca1e7fd 100644 (file)
@@ -33,6 +33,12 @@ public class TestSXSSFCell extends BaseTestCell {
         super(SXSSFITestDataProvider.instance);
     }
 
+
+    @Override
+    public void tearDown(){
+        SXSSFITestDataProvider.instance.cleanup();
+    }
+
     /**
      * this test involves evaluation of formulas which isn't supported for SXSSF
      */
@@ -82,7 +88,7 @@ public class TestSXSSFCell extends BaseTestCell {
         Workbook xwb = new XSSFWorkbook();
         Cell xCell = xwb.createSheet().createRow(0).createCell(0);
 
-        Workbook swb = new SXSSFWorkbook();
+        Workbook swb = SXSSFITestDataProvider.instance.createWorkbook();
         Cell sCell = swb.createSheet().createRow(0).createCell(0);
 
         StringBuffer sb = new StringBuffer();
index 7184e51c1fa125893b3ddce10ae0c724468e7055..9cad4c10cf590af8bf6eeb625be6c84db52b228e 100644 (file)
@@ -33,4 +33,10 @@ public class TestSXSSFHyperlink extends BaseTestHyperlink {
         super(SXSSFITestDataProvider.instance);
     }
 
+
+    @Override
+    public void tearDown(){
+        SXSSFITestDataProvider.instance.cleanup();
+    }
+
 }
\ No newline at end of file
index 1a0ff3bfa2b3d5ea11589b80eed04515081917d2..9533215a714b5ea3e6a4325681b0843819167959 100644 (file)
@@ -32,6 +32,12 @@ public final class TestSXSSFRow extends BaseTestRow {
         super(SXSSFITestDataProvider.instance);
     }
 
+
+    @Override
+    public void tearDown(){
+        SXSSFITestDataProvider.instance.cleanup();
+    }
+
     public void testRowBounds() {
         baseTestRowBounds(SpreadsheetVersion.EXCEL2007.getLastRowIndex());
     }
index f9d8fc09f211c2a1cff23f537c759323ac8b7e8d..2116a0be523c0b1ce1931c02ff166ce4cca16144 100644 (file)
@@ -29,6 +29,13 @@ public class TestSXSSFSheet extends BaseTestSheet {
         super(SXSSFITestDataProvider.instance);
     }
 
+
+    @Override
+    public void tearDown(){
+        SXSSFITestDataProvider.instance.cleanup();
+    }
+
+
     /**
      * cloning of sheets is not supported in SXSSF
      */
index ef9149cbf8e1738d8b66e7b83e39e6af67cd12aa..0d4a7d3c126c3bfa9f32ecfe45a71f21aa6cdc79 100644 (file)
@@ -27,11 +27,17 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import java.io.File;
 
 public final class TestSXSSFWorkbook extends BaseTestWorkbook {
+    public static final SXSSFITestDataProvider _testDataProvider = SXSSFITestDataProvider.instance;
 
-       public TestSXSSFWorkbook() {
-               super(SXSSFITestDataProvider.instance);
+    public TestSXSSFWorkbook() {
+               super(_testDataProvider);
        }
 
+    @Override
+    public void tearDown(){
+        _testDataProvider.cleanup();
+    }
+
     /**
      * cloning of sheets is not supported in SXSSF
      */
@@ -59,19 +65,23 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
                     "Only XSSFCells can be evaluated.", e.getMessage());
         }
     }
-    
+
     public void testExistingWorkbook() {
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
        xssfWorkbook.createSheet("S1");
        SXSSFWorkbook wb = new SXSSFWorkbook(xssfWorkbook);
        xssfWorkbook = (XSSFWorkbook) SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
-       wb = new SXSSFWorkbook(xssfWorkbook);
+       wb.dispose();
+
+        wb = new SXSSFWorkbook(xssfWorkbook);
        assertEquals(1, wb.getNumberOfSheets());
        Sheet sheet  = wb.getSheetAt(0);
        assertNotNull(sheet);
        assertEquals("S1", sheet.getSheetName());
+        wb.dispose();
+
     }
-    
+
     public void testAddToExistingWorkbook() {
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
        xssfWorkbook.createSheet("S1");
@@ -81,26 +91,28 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
        cell.setCellValue("value 2_1_1");
        SXSSFWorkbook wb = new SXSSFWorkbook(xssfWorkbook);
        xssfWorkbook = (XSSFWorkbook) SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
-       wb = new SXSSFWorkbook(xssfWorkbook);
-       
+        wb.dispose();
+
+        wb = new SXSSFWorkbook(xssfWorkbook);
+
        // Add a row to the existing empty sheet
        Sheet sheet1 = wb.getSheetAt(0);
        Row row1_1 = sheet1.createRow(1);
        Cell cell1_1_1 = row1_1.createCell(1);
        cell1_1_1.setCellValue("value 1_1_1");
-       
+
        // Add a row to the existing non-empty sheet
        Sheet sheet2 = wb.getSheetAt(1);
        Row row2_2 = sheet2.createRow(2);
        Cell cell2_2_1 = row2_2.createCell(1);
        cell2_2_1.setCellValue("value 2_2_1");
-       
+
        // Add a sheet with one row
        Sheet sheet3 = wb.createSheet("S3");
        Row row3_1 = sheet3.createRow(1);
        Cell cell3_1_1 = row3_1.createCell(1);
        cell3_1_1.setCellValue("value 3_1_1");
-       
+
        xssfWorkbook = (XSSFWorkbook) SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
        assertEquals(3, xssfWorkbook.getNumberOfSheets());
        // Verify sheet 1
@@ -145,6 +157,7 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
         File tmp = wr.getTempFile();
         assertTrue(tmp.getName().startsWith("poi-sxssf-sheet"));
         assertTrue(tmp.getName().endsWith(".xml"));
+        wb.dispose();
 
         wb = new SXSSFWorkbook();
         wb.setCompressTempFiles(true);
@@ -154,6 +167,7 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
         tmp = wr.getTempFile();
         assertTrue(tmp.getName().startsWith("poi-sxssf-sheet-xml"));
         assertTrue(tmp.getName().endsWith(".gz"));
+        wb.dispose();
 
         //Test escaping of Unicode control characters
         wb = new SXSSFWorkbook();
@@ -162,11 +176,13 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
         Cell cell = xssfWorkbook.getSheet("S1").getRow(0).getCell(0);
         assertEquals("value?", cell.getStringCellValue());
 
+        wb.dispose();
+
     }
-    
+
     public void testGZipSheetdataWriter(){
-        Workbook wb = new SXSSFWorkbook();
-        ((SXSSFWorkbook)wb).setCompressTempFiles(true);
+        SXSSFWorkbook wb = new SXSSFWorkbook();
+        wb.setCompressTempFiles(true);
         int rowNum = 1000;
         int sheetNum = 5;
         for(int i = 0; i < sheetNum; i++){
@@ -175,7 +191,7 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
                 Row row = sh.createRow(j);
                 Cell cell1 = row.createCell(0);
                 cell1.setCellValue(new CellReference(cell1).formatAsString());
-                
+
                 Cell cell2 = row.createCell(1);
                 cell2.setCellValue(i);
 
@@ -184,9 +200,9 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
             }
         }
 
-        wb = SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
+        XSSFWorkbook xwb = (XSSFWorkbook)SXSSFITestDataProvider.instance.writeOutAndReadBack(wb);
         for(int i = 0; i < sheetNum; i++){
-            Sheet sh = wb.getSheetAt(i);
+            Sheet sh = xwb.getSheetAt(i);
             assertEquals("sheet" + i, sh.getSheetName());
             for(int j = 0; j < rowNum; j++){
                 Row row = sh.getRow(j);
@@ -202,7 +218,50 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook {
             }
         }
 
-        
+        wb.dispose();
+
     }
 
+    static void assertWorkbookDispose(SXSSFWorkbook wb)
+    {
+        int rowNum = 1000;
+        int sheetNum = 5;
+        for(int i = 0; i < sheetNum; i++){
+            Sheet sh = wb.createSheet("sheet" + i);
+            for(int j = 0; j < rowNum; j++){
+                Row row = sh.createRow(j);
+                Cell cell1 = row.createCell(0);
+                cell1.setCellValue(new CellReference(cell1).formatAsString());
+
+                Cell cell2 = row.createCell(1);
+                cell2.setCellValue(i);
+
+                Cell cell3 = row.createCell(2);
+                cell3.setCellValue(j);
+            }
+        }
+
+        for (SXSSFSheet sheet : wb._sxFromXHash.keySet()) {
+            assertTrue(sheet.getSheetDataWriter().getTempFile().exists());
+        }
+
+        assertTrue(wb.dispose());
+
+        for (SXSSFSheet sheet : wb._sxFromXHash.keySet()) {
+            assertFalse(sheet.getSheetDataWriter().getTempFile().exists());
+        }
+    }
+
+    public void testWorkbookDispose()
+    {
+        SXSSFWorkbook wb = new SXSSFWorkbook();
+        // the underlying writer is SheetDataWriter
+        assertWorkbookDispose(wb);
+
+        wb = new SXSSFWorkbook();
+        wb.setCompressTempFiles(true);
+        // the underlying writer is GZIPSheetDataWriter
+        assertWorkbookDispose(wb);
+
+    }
 }