Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows fixes for leaking file handles #37 #38

Merged
merged 7 commits into from
May 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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