[PATCH 3 of 3 STABLE] localrepo.wwrite: use unlinkopened instead of os.unlink

Adrian Buehlmann adrian at cadifra.com
Fri Dec 3 02:40:18 CST 2010

On 2010-12-03 03:56, Greg Ward wrote:
> On Wed, Dec 1, 2010 at 7:53 AM, Adrian Buehlmann <adrian at cadifra.com> wrote:
>> # HG changeset patch
>> # User Adrian Buehlmann <adrian at cadifra.com>
>> # Date 1291199708 -3600
>> # Branch stable
>> # Node ID 18d06beed4a794331427e89f3828cb9408012b6a
>> # Parent  52f97fe43a6be964691d78a0147f6650855fc303
>> localrepo.wwrite: use unlinkopened instead of os.unlink
>> Windows delays deleting open files, preventing recreation under
>> the same name until the file is closed.
>> diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
>> --- a/mercurial/localrepo.py
>> +++ b/mercurial/localrepo.py
>> @@ -618,7 +618,7 @@ class localrepository(repo.repository):
>>     def wwrite(self, filename, data, flags):
>>         data = self._filter(self._decodefilterpats, filename, data)
>>         try:
>> -            os.unlink(self.wjoin(filename))
>> +            util.unlinkopened(self.wjoin(filename))
> Does this fix a bug? Or is just a "nice to have"?

It does. The original code causes another instance of Issue2524.

BTW, I hope you have seen that util.unlinkopened is an alias for os.unlink
on Linux.

BTW2, Matt wants to have that solved in one place. This will take time,
since we have to review every single os.unlink.

Some further (possibly redundant) explanations:

localrepo.wwrite is one of the few places with the biggest damage
potential regarding Issue2524, since all working dir writes go through
this doing posixfile(f, 'w') later on in the opener. Which Mercurial now
has been doing for a long time already.

This also explains the reports we had about vanished files with Virus
scanners very well. But please note that a virus scanncer is not a
requirement to trigger this kind of file loss. They just increase
the file loss probability to a point where users start to notice it.

The whole thing is racy to reproduce with real uses cases. For white box
testing, it is easy to reproduce on Windows.

On Windows, in one shell start python and open a working dir file
with posixfile:

  $ python
  Python 2.6.5 (r265:79096, Mar 19 2010, 21:48:26) [MSC v.1500 32 bit (Intel)] on win32
  Type "help", "copyright", "credits" or "license" for more information.
  >>> from mercurial.windows import posixfile
  >>> fd = posixfile('a.txt')

then in a second cmd.exe shell instance do:

  $ hg version
  Mercurial Distributed SCM (version 1.7.2)
  (see http://mercurial.selenic.com for more information)

  $ hg parent -q

  $ hg update 16
  abort: C:\Users\adi\hgrepos\tests\c\a.txt: Access is denied

Now, the directory entry for 'a.txt' still shows up:

  $ dir /w
   Volume in drive C has no label.
   Volume Serial Number is F80E-0A52

   Directory of C:\Users\adi\hgrepos\tests\c

  [.]     [..]    [.hg]   a.txt   b.txt
                 2 File(s)             18 bytes
                 3 Dir(s)  473'414'610'944 bytes free

But the file a.txt is now a ghost in "scheduled delete" state [1].

In that state you can't even read the file ("type" is the Windows equivalent
of "cat" for the Linux fraction):

  $ type a.txt
  Access is denied.

Now, let's close the file handle in the first shell:

  >>> fd.close()

Another look in the second command shell now shows the file is gone from
the directory:

  $ dir /w
   Volume in drive C has no label.
   Volume Serial Number is F80E-0A52

   Directory of C:\Users\adi\hgrepos\tests\c

  [.]     [..]    [.hg]   b.txt
                 1 File(s)              6 bytes
                 3 Dir(s)  473'414'606'848 bytes free

And status agrees with that:

  $ hg status
  ! a.txt


In short: doing a 'hg update' while another process is reading the file
nukes the file on Windows.

[1] http://mercurial.selenic.com/wiki/UnlinkingFilesOnWindows

More information about the Mercurial-devel mailing list