mirror of
https://github.com/clearml/clearml-agent
synced 2025-01-31 09:06:52 +00:00
194 lines
6.8 KiB
Python
194 lines
6.8 KiB
Python
import os
|
|
import sys
|
|
|
|
|
|
class exceptions:
|
|
class BaseLockException(Exception):
|
|
# Error codes:
|
|
LOCK_FAILED = 1
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.fh = kwargs.pop('fh', None)
|
|
Exception.__init__(self, *args, **kwargs)
|
|
|
|
class LockException(BaseLockException):
|
|
pass
|
|
|
|
class AlreadyLocked(BaseLockException):
|
|
pass
|
|
|
|
class FileToLarge(BaseLockException):
|
|
pass
|
|
|
|
|
|
class constants:
|
|
# The actual tests will execute the code anyhow so the following code can
|
|
# safely be ignored from the coverage tests
|
|
if os.name == 'nt': # pragma: no cover
|
|
import msvcrt
|
|
|
|
LOCK_EX = 0x1 #: exclusive lock
|
|
LOCK_SH = 0x2 #: shared lock
|
|
LOCK_NB = 0x4 #: non-blocking
|
|
LOCK_UN = msvcrt.LK_UNLCK #: unlock
|
|
|
|
LOCKFILE_FAIL_IMMEDIATELY = 1
|
|
LOCKFILE_EXCLUSIVE_LOCK = 2
|
|
|
|
elif os.name == 'posix': # pragma: no cover
|
|
import fcntl
|
|
|
|
LOCK_EX = fcntl.LOCK_EX #: exclusive lock
|
|
LOCK_SH = fcntl.LOCK_SH #: shared lock
|
|
LOCK_NB = fcntl.LOCK_NB #: non-blocking
|
|
LOCK_UN = fcntl.LOCK_UN #: unlock
|
|
|
|
else: # pragma: no cover
|
|
raise RuntimeError('PortaLocker only defined for nt and posix platforms')
|
|
|
|
|
|
if os.name == 'nt': # pragma: no cover
|
|
import msvcrt
|
|
|
|
if sys.version_info.major == 2:
|
|
lock_length = -1
|
|
else:
|
|
lock_length = int(2**31 - 1)
|
|
|
|
def lock(file_, flags):
|
|
if flags & constants.LOCK_SH:
|
|
import win32file
|
|
import pywintypes
|
|
import winerror
|
|
__overlapped = pywintypes.OVERLAPPED()
|
|
if sys.version_info.major == 2:
|
|
if flags & constants.LOCK_NB:
|
|
mode = constants.LOCKFILE_FAIL_IMMEDIATELY
|
|
else:
|
|
mode = 0
|
|
|
|
else:
|
|
if flags & constants.LOCK_NB:
|
|
mode = msvcrt.LK_NBRLCK
|
|
else:
|
|
mode = msvcrt.LK_RLCK
|
|
|
|
# is there any reason not to reuse the following structure?
|
|
hfile = win32file._get_osfhandle(file_.fileno())
|
|
try:
|
|
win32file.LockFileEx(hfile, mode, 0, -0x10000, __overlapped)
|
|
except pywintypes.error as exc_value:
|
|
# error: (33, 'LockFileEx', 'The process cannot access the file
|
|
# because another process has locked a portion of the file.')
|
|
if exc_value.winerror == winerror.ERROR_LOCK_VIOLATION:
|
|
raise exceptions.LockException(
|
|
exceptions.LockException.LOCK_FAILED,
|
|
exc_value.strerror,
|
|
fh=file_)
|
|
else:
|
|
# Q: Are there exceptions/codes we should be dealing with
|
|
# here?
|
|
raise
|
|
else:
|
|
mode = constants.LOCKFILE_EXCLUSIVE_LOCK
|
|
if flags & constants.LOCK_NB:
|
|
mode |= constants.LOCKFILE_FAIL_IMMEDIATELY
|
|
|
|
if flags & constants.LOCK_NB:
|
|
mode = msvcrt.LK_NBLCK
|
|
else:
|
|
mode = msvcrt.LK_LOCK
|
|
|
|
# windows locks byte ranges, so make sure to lock from file start
|
|
try:
|
|
savepos = file_.tell()
|
|
if savepos:
|
|
# [ ] test exclusive lock fails on seek here
|
|
# [ ] test if shared lock passes this point
|
|
file_.seek(0)
|
|
# [x] check if 0 param locks entire file (not documented in
|
|
# Python)
|
|
# [x] fails with "IOError: [Errno 13] Permission denied",
|
|
# but -1 seems to do the trick
|
|
|
|
try:
|
|
msvcrt.locking(file_.fileno(), mode, lock_length)
|
|
except IOError as exc_value:
|
|
# [ ] be more specific here
|
|
raise exceptions.LockException(
|
|
exceptions.LockException.LOCK_FAILED,
|
|
exc_value.strerror,
|
|
fh=file_)
|
|
finally:
|
|
if savepos:
|
|
file_.seek(savepos)
|
|
except IOError as exc_value:
|
|
raise exceptions.LockException(
|
|
exceptions.LockException.LOCK_FAILED, exc_value.strerror,
|
|
fh=file_)
|
|
|
|
def unlock(file_):
|
|
try:
|
|
savepos = file_.tell()
|
|
if savepos:
|
|
file_.seek(0)
|
|
|
|
try:
|
|
msvcrt.locking(file_.fileno(), constants.LOCK_UN, lock_length)
|
|
except IOError as exc_value:
|
|
if exc_value.strerror == 'Permission denied':
|
|
import pywintypes
|
|
import win32file
|
|
import winerror
|
|
__overlapped = pywintypes.OVERLAPPED()
|
|
hfile = win32file._get_osfhandle(file_.fileno())
|
|
try:
|
|
win32file.UnlockFileEx(
|
|
hfile, 0, -0x10000, __overlapped)
|
|
except pywintypes.error as exc_value:
|
|
if exc_value.winerror == winerror.ERROR_NOT_LOCKED:
|
|
# error: (158, 'UnlockFileEx',
|
|
# 'The segment is already unlocked.')
|
|
# To match the 'posix' implementation, silently
|
|
# ignore this error
|
|
pass
|
|
else:
|
|
# Q: Are there exceptions/codes we should be
|
|
# dealing with here?
|
|
raise
|
|
else:
|
|
raise exceptions.LockException(
|
|
exceptions.LockException.LOCK_FAILED,
|
|
exc_value.strerror,
|
|
fh=file_)
|
|
finally:
|
|
if savepos:
|
|
file_.seek(savepos)
|
|
except IOError as exc_value:
|
|
raise exceptions.LockException(
|
|
exceptions.LockException.LOCK_FAILED, exc_value.strerror,
|
|
fh=file_)
|
|
|
|
elif os.name == 'posix': # pragma: no cover
|
|
import fcntl
|
|
|
|
def lock(file_, flags):
|
|
locking_exceptions = IOError,
|
|
try: # pragma: no cover
|
|
locking_exceptions += BlockingIOError,
|
|
except NameError: # pragma: no cover
|
|
pass
|
|
|
|
try:
|
|
fcntl.flock(file_.fileno(), flags)
|
|
except locking_exceptions as exc_value:
|
|
# The exception code varies on different systems so we'll catch
|
|
# every IO error
|
|
raise exceptions.LockException(exc_value, fh=file_)
|
|
|
|
def unlock(file_):
|
|
fcntl.flock(file_.fileno(), constants.LOCK_UN)
|
|
|
|
else: # pragma: no cover
|
|
raise RuntimeError('PortaLocker only defined for nt and posix platforms')
|