Skip to content

Commit

Permalink
Merge pull request #38 from stuertz/windows_fixes
Browse files Browse the repository at this point in the history
Windows fixes for leaking file handles #37
  • Loading branch information
Byron authored May 28, 2017
2 parents fd2eba4 + 7bced78 commit 4ddd1a3
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 8 deletions.
12 changes: 12 additions & 0 deletions gitdb/pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ def __init__(self, indexpath):
super(PackIndexFile, self).__init__()
self._indexpath = indexpath

def close(self):
mman.force_map_handle_removal_win(self._indexpath)
self._cursor = None

def _set_cache_(self, attr):
if attr == "_packfile_checksum":
self._packfile_checksum = self._cursor.map()[-40:-20]
Expand Down Expand Up @@ -527,6 +531,10 @@ class PackFile(LazyMixin):
def __init__(self, packpath):
self._packpath = packpath

def close(self):
mman.force_map_handle_removal_win(self._packpath)
self._cursor = None

def _set_cache_(self, attr):
# we fill the whole cache, whichever attribute gets queried first
self._cursor = mman.make_cursor(self._packpath).use_region()
Expand Down Expand Up @@ -668,6 +676,10 @@ def __init__(self, pack_or_index_path):
self._index = self.IndexFileCls("%s.idx" % basename) # PackIndexFile instance
self._pack = self.PackFileCls("%s.pack" % basename) # corresponding PackFile instance

def close(self):
self._index.close()
self._pack.close()

def _set_cache_(self, attr):
# currently this can only be _offset_map
# TODO: make this a simple sorted offset array which can be bisected
Expand Down
11 changes: 11 additions & 0 deletions gitdb/test/db/test_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,22 @@
from gitdb.db import PackedDB

from gitdb.exc import BadObject, AmbiguousObjectName
from gitdb.util import mman

import os
import random
import sys

from nose.plugins.skip import SkipTest

class TestPackDB(TestDBBase):

@with_rw_directory
@with_packs_rw
def test_writing(self, path):
if sys.platform == "win32":
raise SkipTest("FIXME: Currently fail on windows")

pdb = PackedDB(path)

# on demand, we init our pack cache
Expand All @@ -30,6 +36,11 @@ def test_writing(self, path):
# packs removed - rename a file, should affect the glob
pack_path = pdb.entities()[0].pack().path()
new_pack_path = pack_path + "renamed"
if sys.platform == "win32":
# While using this function, we are not allowed to have any handle
# to this path, which is currently not the case. The pack caching
# does still have a handle :-(
mman.force_map_handle_removal_win(pack_path)
os.rename(pack_path, new_pack_path)

pdb.update_cache(force=True)
Expand Down
5 changes: 3 additions & 2 deletions gitdb/test/performance/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from gitdb.db import LooseObjectDB
from gitdb import IStream

from gitdb.util import bin_to_hex
from gitdb.util import bin_to_hex, remove
from gitdb.fun import chunk_size

from time import time
Expand Down Expand Up @@ -104,5 +104,6 @@ def test_large_data_streaming(self, path):
(size_kib, desc, cs_kib, elapsed_readchunks, size_kib / (elapsed_readchunks or 1)), file=sys.stderr)

# del db file so we keep something to do
os.remove(db_file)
ostream = None # To release the file handle (win)
remove(db_file)
# END for each randomization factor
7 changes: 5 additions & 2 deletions gitdb/test/test_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,11 @@ def rewind_streams():
assert os.path.getsize(ppath) > 100

# verify pack
pf = PackFile(ppath) # FIXME: Leaks file-pointer(s)!
pf = PackFile(ppath)
assert pf.size() == len(pack_objs)
assert pf.version() == PackFile.pack_version_default
assert pf.checksum() == pack_sha
pf.close()

# verify index
if ipath is not None:
Expand All @@ -231,6 +232,7 @@ def rewind_streams():
assert idx.packfile_checksum() == pack_sha
assert idx.indexfile_checksum() == index_sha
assert idx.size() == len(pack_objs)
idx.close()
# END verify files exist
# END for each packpath, indexpath pair

Expand All @@ -245,7 +247,8 @@ def rewind_streams():
# END for each crc mode
# END for each info
assert count == len(pack_objs)

entity.close()

def test_pack_64(self):
# TODO: hex-edit a pack helping us to verify that we can handle 64 byte offsets
# of course without really needing such a huge pack
Expand Down
27 changes: 23 additions & 4 deletions gitdb/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import mmap
import sys
import time
import errno

from io import BytesIO
Expand Down Expand Up @@ -58,7 +59,6 @@ def unpack_from(fmt, data, offset=0):
isdir = os.path.isdir
isfile = os.path.isfile
rename = os.rename
remove = os.remove
dirname = os.path.dirname
basename = os.path.basename
join = os.path.join
Expand All @@ -67,6 +67,25 @@ def unpack_from(fmt, data, offset=0):
close = os.close
fsync = os.fsync


def _retry(func, *args, **kwargs):
# Wrapper around functions, that are problematic on "Windows". Sometimes
# the OS or someone else has still a handle to the file
if sys.platform == "win32":
for _ in range(10):
try:
return func(*args, **kwargs)
except Exception:
time.sleep(0.1)
return func(*args, **kwargs)
else:
return func(*args, **kwargs)


def remove(*args, **kwargs):
return _retry(os.remove, *args, **kwargs)


# Backwards compatibility imports
from gitdb.const import (
NULL_BIN_SHA,
Expand Down Expand Up @@ -321,7 +340,7 @@ def open(self, write=False, stream=False):
self._fd = os.open(self._filepath, os.O_RDONLY | binary)
except:
# assure we release our lockfile
os.remove(self._lockfilepath())
remove(self._lockfilepath())
raise
# END handle lockfile
# END open descriptor for reading
Expand Down Expand Up @@ -365,7 +384,7 @@ def _end_writing(self, successful=True):
# on windows, rename does not silently overwrite the existing one
if sys.platform == "win32":
if isfile(self._filepath):
os.remove(self._filepath)
remove(self._filepath)
# END remove if exists
# END win32 special handling
os.rename(lockfile, self._filepath)
Expand All @@ -376,7 +395,7 @@ def _end_writing(self, successful=True):
chmod(self._filepath, int("644", 8))
else:
# just delete the file so far, we failed
os.remove(lockfile)
remove(lockfile)
# END successful handling

#} END utilities

0 comments on commit 4ddd1a3

Please sign in to comment.