Skip to content

Commit ac9ac55

Browse files
authored
Merge pull request #513 from expobrain/master
Use flock() for file locking
2 parents 4e5ef73 + 20b4f49 commit ac9ac55

File tree

6 files changed

+81
-9
lines changed

6 files changed

+81
-9
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ nbproject
1414
.DS_Store
1515
/*egg-info
1616
/.tox
17+
.cache

Diff for: AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ Contributors are:
1515
-Jonathan Chu <jonathan.chu _at_ me.com>
1616
-Vincent Driessen <me _at_ nvie.com>
1717
-Phil Elson <pelson _dot_ pub _at_ gmail.com>
18+
-Daniele Esposti <daniele.esposti _at_ gmail.com>
1819

1920
Portions derived from other open source works and are clearly marked.

Diff for: git/test/test_util.py

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66

77
import tempfile
8+
import gc
89

910
from git.test.lib import (
1011
TestBase,
@@ -80,6 +81,7 @@ def test_lock_file(self):
8081

8182
# auto-release on destruction
8283
del(other_lock_file)
84+
gc.collect()
8385
lock_file._obtain_lock_or_raise()
8486
lock_file._release_lock()
8587

Diff for: git/util.py

+68-9
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,56 @@
4848

4949
#{ Utility Methods
5050

51+
if platform.system() == 'Windows':
52+
# This code is a derivative work of Portalocker http://code.activestate.com/recipes/65203/
53+
import win32con
54+
import win32file
55+
import pywintypes
56+
57+
LOCK_EX = win32con.LOCKFILE_EXCLUSIVE_LOCK
58+
LOCK_SH = 0 # the default
59+
LOCK_NB = win32con.LOCKFILE_FAIL_IMMEDIATELY
60+
LOCK_UN = 1 << 2
61+
62+
__overlapped = pywintypes.OVERLAPPED()
63+
64+
def flock(fd, flags=0):
65+
hfile = win32file._get_osfhandle(fd)
66+
67+
if flags & LOCK_UN != 0:
68+
# Unlock file descriptor
69+
try:
70+
win32file.UnlockFileEx(hfile, 0, -0x10000, __overlapped)
71+
except pywintypes.error as exc_value:
72+
# error: (158, 'UnlockFileEx', 'The segment is already unlocked.')
73+
# To match the 'posix' implementation, silently ignore this error
74+
if exc_value[0] == 158:
75+
pass
76+
else:
77+
# Q: Are there exceptions/codes we should be dealing with here?
78+
raise
79+
80+
elif flags & LOCK_EX != 0:
81+
# Lock file
82+
try:
83+
win32file.LockFileEx(hfile, flags, 0, -0x10000, __overlapped)
84+
except pywintypes.error as exc_value:
85+
if exc_value[0] == 33:
86+
# error: (33, 'LockFileEx',
87+
# 'The process cannot access the file because another process has locked
88+
# a portion of the file.')
89+
raise IOError(33, exc_value[2])
90+
else:
91+
# Q: Are there exceptions/codes we should be dealing with here?
92+
raise
93+
94+
else:
95+
raise NotImplementedError("Unsupported set of bitflags {}".format(bin(flags)))
96+
97+
98+
else:
99+
from fcntl import flock, LOCK_UN, LOCK_EX, LOCK_NB
100+
51101

52102
def unbare_repo(func):
53103
"""Methods with this decorator raise InvalidGitRepositoryError if they
@@ -555,9 +605,10 @@ class LockFile(object):
555605
As we are a utility class to be derived from, we only use protected methods.
556606
557607
Locks will automatically be released on destruction"""
558-
__slots__ = ("_file_path", "_owns_lock")
608+
__slots__ = ("_file_path", "_owns_lock", "_file_descriptor")
559609

560610
def __init__(self, file_path):
611+
self._file_descriptor = None
561612
self._file_path = file_path
562613
self._owns_lock = False
563614

@@ -579,20 +630,21 @@ def _obtain_lock_or_raise(self):
579630
:raise IOError: if a lock was already present or a lock file could not be written"""
580631
if self._has_lock():
581632
return
633+
582634
lock_file = self._lock_file_path()
583-
if os.path.isfile(lock_file):
584-
raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" %
585-
(self._file_path, lock_file))
586635

636+
# Create file and lock
587637
try:
588-
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
638+
flags = os.O_CREAT
589639
if is_win:
590640
flags |= os.O_SHORT_LIVED
591641
fd = os.open(lock_file, flags, 0)
592-
os.close(fd)
593642
except OSError as e:
594643
raise IOError(str(e))
595644

645+
flock(fd, LOCK_EX | LOCK_NB)
646+
647+
self._file_descriptor = fd
596648
self._owns_lock = True
597649

598650
def _obtain_lock(self):
@@ -605,14 +657,21 @@ def _release_lock(self):
605657
if not self._has_lock():
606658
return
607659

660+
fd = self._file_descriptor
661+
lock_file = self._lock_file_path()
662+
663+
flock(fd, LOCK_UN)
664+
os.close(fd)
665+
608666
# if someone removed our file beforhand, lets just flag this issue
609667
# instead of failing, to make it more usable.
610-
lfp = self._lock_file_path()
611668
try:
612-
rmfile(lfp)
669+
rmfile(lock_file)
613670
except OSError:
614671
pass
672+
615673
self._owns_lock = False
674+
self._file_descriptor = None
616675

617676

618677
class BlockingLockFile(LockFile):
@@ -647,7 +706,7 @@ def _obtain_lock(self):
647706
try:
648707
super(BlockingLockFile, self)._obtain_lock()
649708
except IOError:
650-
# synity check: if the directory leading to the lockfile is not
709+
# sanity check: if the directory leading to the lockfile is not
651710
# readable anymore, raise an execption
652711
curtime = time.time()
653712
if not os.path.isdir(os.path.dirname(self._lock_file_path())):

Diff for: setup.py

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import logging
1414
import os
1515
import sys
16+
import platform
1617
from os import path
1718

1819
with open(path.join(path.dirname(__file__), 'VERSION')) as v:
@@ -21,6 +22,10 @@
2122
with open('requirements.txt') as reqs_file:
2223
requirements = reqs_file.read().splitlines()
2324

25+
if platform.system() == 'Windows':
26+
with open('win32-requirements.txt') as reqs_file:
27+
requirements += reqs_file.read().splitlines()
28+
2429

2530
class build_py(_build_py):
2631

@@ -65,6 +70,8 @@ def _stamp_version(filename):
6570
print("WARNING: Couldn't find version line in file %s" % filename, file=sys.stderr)
6671

6772
install_requires = ['gitdb >= 0.6.4']
73+
if platform.system() == 'Windows':
74+
install_requires.append("pypiwin32 >= 219")
6875
extras_require = {
6976
':python_version == "2.6"': ['ordereddict'],
7077
}

Diff for: win32-requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-r requirements.txt
2+
pypiwin32 >= 219

0 commit comments

Comments
 (0)