XMLDocument + MemoryStream + Zip.UpdateEntry + Zip.Save = IOException, file in use.

Feb 24, 2011 at 5:22 AM
Edited Feb 24, 2011 at 4:33 PM

As per request, this is the official resurrection of the "File in Use" exception arising when working with MemoryStreams that have had XMLDocument .Save() on the stream explicitly.

Previous report and example are in http://dotnetzip.codeplex.com/discussions/44663

Notably, I can always reproduce this behavior, namely, unless I null the XMLDoc and call out GC.Collect the subsequent zip.Save() will always fail. I have tried staggering the thread (simulating "old" computer that seemed to work for previous poster), sleeping, and even copying memory streams to no avail. XMLDocument has no IDisposable or Close implementations, so "using" is out the window.

FYI,Windows7 + VS2010 Express C# with 4.0NETClient target. Also using latest available DotNetZip (reduced) assembly dll.

Here is the code:

MemoryStream stream_core = new MemoryStream();
XmlDocument xmldoc_core = new XmlDocument();

// We actually check if extracting worked in the working sample
ZipEntry e_core = zip["\\somepath\\somefile.xml"];
e_core.Extract(stream_core);
stream_core.Seek(0, SeekOrigin.Begin);

// This is validated too
xmldoc_core.Load(stream_core);

// This class function takes the xmldocument and modifies ut by appending some nodes, nothing fancy.
xmldoc_core = update_document(xmldoc_core, "1", "2", "3", textBoxRevision.Text);
// xmldoc is valid, commence pushback

MemoryStream stream_o_core = new MemoryStream();

// save to another stream
xmldoc_core.Save(stream_o_core);

// If this is uncommented, Saving works
// xmldoc_core = null;
// GC.Collect();

// Rewind
stream_o_core.Seek(0, SeekOrigin.Begin);

// Remove and re-add
zip.RemoveEntry("\\somepath\\somefile.xml");
zip.AddEntry("\\somepath\\somefile.xml", stream_o_core);

// This will fail with IOException = sharing violation, file in use
zip.Save();

Please let me know what other details you need.

Respectfully,

K.

Feb 28, 2011 at 7:45 PM

Hi kommi,

I think the problem is that internally the XmlDocument.Save method wraps your stream in an Xml.XmlDOMWriter to perform the save, but it doesn't call Close / Dispose afterwards. I don't know if this is a bug, or whether it's by design, but it's probably what is causing the sharing violation by holding a reference to your stream_o_core until you kill the XmlDOMWriter with an explicit call to GC.Collect().

It sounds like the garbage collector is probably more aggressive on slower machines (maybe because of less memory?) and calls GC.Collect() itself before the code gets to enter zip.Save().

Hope this helps,

Mike

Feb 28, 2011 at 10:30 PM

Uhm, that would make sense, but what does .Save() of DotNetZip is actually doing to the MemoryStream to get the sharing violation? Should I instead explicitly instantiate an XMLWriter, write to stream, close the Writer and then pass the MemoryStream to DotNetZip? (XMLWriter does have a close)

Mar 1, 2011 at 12:14 AM

Hi kommi,

It's probably just trying to read data from the stream to zip it up, but if the XmlDocument it stream locked with "Sharing = None" then the zip object won't be able to read from it until the XmlDOMWriter is closed. I don't really have time at the moment to dig too deep into it at the moment, but you could try replicating the behaviour of XmlDocument.Save, and closing the XmlDOMWriter afterwards. Or you could copy the data from stream_o_core into a separate memorystream and pass that to DotNetZip.

I can take another look if you can post a stack trace of the exception, but your best bet might just be to live with GC.Collect for now.

Cheers,

M

Mar 1, 2011 at 7:43 PM

Ill try your suggestions, it's very hard to debug though: it happens sporadically, for example, right now I can not trigger the sharing violation to occur at all. Speaking of which, if stream is locked with no sharing, then I wouldn't be able to clone it either, no?

Mar 1, 2011 at 8:39 PM

Possibly. I guess it depends on what locks XmlDOMWriter holds vs the ones DotNetZip wants. e.g. if DotNetZip wants Write even though it only needs Read then you might be able to get away with copying just using Read access. You might have to suck it and see...

Implementing your own version of XmlDocument.Save is probably the "best" workaround. It's only half a dozen lines of code anyway.

M

Coordinator
Mar 9, 2011 at 3:49 PM

Seems to me you would want to try using the ZipFile.AddEntry() method that accepts a WriteDelegate

In that case you would not need the extra stream.  I guess the code would look something like this:

          XmlDocument xmldoc_core = new XmlDocument();
          using (MemoryStream ms1 = new MemoryStream()) 
          {
              // We actually check if extracting worked in the working sample
              zip["\\somepath\\somefile.xml"].Extract(ms); 
              ms1.Seek(0, SeekOrigin.Begin);

              xmldoc_core.Load(ms1);
          }
          // This class function takes the xmldocument and modifies it by
          // appending some nodes, nothing fancy.
        
          xmldoc_core = update_document(xmldoc_core, "1", "2", "3", textBoxRevision.Text);
          // xmldoc is valid, commence pushback

          // ** Note 1: Why assign the output of the above?  If the method
          // simply modifies the XmlDocument, why do you need to assign
          // the value?  Check your implementation.
        
          // Remove and re-add
          zip.RemoveEntry("\\somepath\\somefile.xml");
          zip.AddEntry("\\somepath\\somefile.xml", (name,stream) => {
                  xmldoc_core.Save(stream);
              });

          // This will fail with IOException = sharing violation, file in use
          zip.Save();