Skip to content

Windows fixes for leaking file handles #37 #38

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

Merged
merged 7 commits into from
May 28, 2017
Merged
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
@@ -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]
@@ -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()
@@ -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
11 changes: 11 additions & 0 deletions gitdb/test/db/test_pack.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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)
5 changes: 3 additions & 2 deletions gitdb/test/performance/test_stream.py
Original file line number Diff line number Diff line change
@@ -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
@@ -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
@@ -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:
@@ -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

@@ -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
27 changes: 23 additions & 4 deletions gitdb/util.py
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
import os
import mmap
import sys
import time
import errno

from io import BytesIO
@@ -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
@@ -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,
@@ -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
@@ -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)
@@ -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