Friday, October 7, 2011

SharpZipLib "Cannot access a closed file" when extracting file stream

Another Friday afternoon riddle. Using ICSharpCode.SharpZipLib to extract a file from a zip via a Stream. The process works fine in a debug build but fails in a release build with the following exception:

System.ObjectDisposedException: Cannot access a closed file.
   at System.IO.FileStream.Seek(Int64 offset, SeekOrigin origin)
   at ICSharpCode.SharpZipLib.Zip.ZipFile.PartialInputStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputBuffer.Fill()
   at ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream.Fill()
   at ICSharpCode.SharpZipLib.Zip.Compression.Streams.InflaterInputStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at xyz.DataExport.Extractor.CopyStream(Stream input, Stream output) in D:\xyz\DataExport\Extractor.cs:line 172

The offending code:

		public Stream GetEntityData(string sourceCsvName)
		{
			// Scan the Zip files looking for the required entity.
			DirectoryInfo dirInfo = new DirectoryInfo(csvBaseDirectory);
			foreach (FileInfo fileInfo in dirInfo.GetFiles("*.zip"))
			{
				Logging.InfoFormat(this, "Searching Zip {0} for {1}.csv", fileInfo, sourceCsvName);

				ZipFile zf = new ZipFile(fileInfo.FullName);
				ZipEntry ze = zf.GetEntry(sourceCsvName + ".csv");
				if (ze != null)
				{
					Stream entryStream = zf.GetInputStream(ze);
					Logging.Info(this, "Opened input zip Stream from: " + fileInfo);

					// Had issues with the csvInputStream closing during processing. Copy into memory
					MemoryStream storeStream = new MemoryStream();
					CopyStream(entryStream, storeStream);
					storeStream.Flush();
					Logging.Info(this, "Closing input zip Stream from: " + fileInfo);
					entryStream.Close();
                                        // Fix by uncommenting the following line
                                        //zf.Close();

					storeStream.Position = 0;

					return storeStream;
				}

			}

			return null;
		}

I suspect that in a release build the garbage collector is being more aggressive efficient and disposing of the ZipFile zf almost immediately which is causing the Stream to close before it can be copied to the MemoryStream.

This seems to concur with the fix I did by explicitly calling zf.Close() after entryStream.Close().
In debug mode the garbage collector was disposing of the ZipFile later on, which is why I had already added code to copy the Zip stream into a MemoryStream.