2

I have a class that takes data from several sources and writes them to a ZIP file. I've benchmarked the class to check if using CompressionLevel.Optimal would be much slower than CompressionLevel.Fastest. But the benchmark throws an exception on different iterations and in different CompressionLevel values each time I run the benchmark.

I started removing the methods that add the file-content step by step until I ended up with the code below (inside the for-loop) which does basically nothing besides creating an empty zip-file and deleting it.

Simplified code:

var o = @"e:\test.zip";
var result = new FileInfo(o);
for (var i = 0; i < 1_000_000; i++)
{
    // Alternate approach
    // using(var archive = ZipFile.Open(o, ZipArchiveMode.Create))
    using (var archive = new ZipArchive(result.OpenWrite(), ZipArchiveMode.Create, false, Encoding.UTF8))
    {
    }

    result.Delete();
}

The loop runs about 100 to 15k iterations on my PC and then throws an IOException when trying to delete the file saying that the file (result) is locked.

So... did I miss something about how to use System.IO.Compression.ZipArchive? There is no close method for ZipArchive and using should dispose/close the archive... I've tried different .NET versions 4.6, 4.6.1, 4.7 and 4.7.2.

EDIT 1:
The result.Delete() is not part of the code that is benchmarked

EDIT 2:
Also tried to play around with Thread.Sleep(5/10/20) after the using block (therefore the result.Delete() to check if the lock persists) but up to 20ms the file is still locked at some point. Didnt tried higher values than 20ms.

EDIT 3:
Can't reprodurce the problem at home. Tried a dozen times at work and the loop never hit 20k iterations. Tried once here and it completed.

EDIT 4:
jdweng (see comments) was right. Thanks! Its somehow related to my "e:" partition on a local hdd. The same code runs fine on my "c:" partition on a local ssd and also on a network share.

Michael
  • 1,931
  • 2
  • 8
  • 22
  • What file is locked? Is it same file each time? Can you use Windows Explorer to copy bad file to a different folder? – jdweng Feb 19 '21 at 10:58
  • Why is `result` declared outside the loop, but deleted inside the loop? Looks a little bit confusing – Pavel Anikhouski Feb 19 '21 at 11:24
  • @jdweng added explanation to my post. The file that is declared outside the loop `@"e:\test.zip"` is locked – Michael Feb 19 '21 at 18:23
  • @PavelAnikhouski tried to provide a mcve, its not the real code – Michael Feb 19 '21 at 18:28
  • 1
    What type of drive is e:\? I wouldn't believe the error message. May of failed for other reasons than locked. – jdweng Feb 20 '21 at 04:41
  • You mean like ssd/hdd? Its a local hdd formatted with NTFS file system @jdweng – Michael Feb 20 '21 at 09:00
  • 1
    You are using a Network Drive. May have issues. Try c:\. A Zip file has similar file structure as a drive which contains Blocks/Sectors. I've found in past zip utilities do not alaways work on a drive that has a different block/sector structure from machine code runs on. 20 years ago at work we were archiving our software project on a file server and than couldn't unzip the file. We were using Win95 and the file server (mapped network dirve) was a linux machine. Zip worked fine when we archived on Win95. – jdweng Feb 20 '21 at 11:43

1 Answers1

0

In my experience files are may not be consistently unlocked when the dispose method for the stream returns. My best guess is that this is due to the file system doing some operation asynchronously. The best solution I have found is to retry the delete operation multiple times. i.e. something like this:

    public static void DeleteRetrying(this FileInfo self, int delayMs = 100, int numberOfAttempts = 3)
    {
        for (int i = 0; i < numberOfAttempts-1; i++)
        {
            try
            {
                self.Delete();
            }
            catch (IOException)
            {
                // Consider making the method async and
                // replace this with Task.Delay
                Thread.Sleep(delayMs);
            }
        }
        // Final attempt, let the exception propagate
        self.Delete();
    }

This is not an ideal solution, and I would love if someone could provide a better solution. But it might be good enough for testing where the impact of a non deleted file would be manageable.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • Thanks. The delete operation is not part of the problem or of the actual code. its part of the benchmark and not related to the problem. Should have added that to my question, sorry for that – Michael Feb 19 '21 at 18:13