Improve implementation of lopen()

Greatly simplify window implementation. Reduce the average number of
system calls used per invocation on OS X and linux.
This commit is contained in:
Kovid Goyal 2016-02-12 13:01:45 +05:30
parent ae6bac5159
commit 40d4224363

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
Perform various initialization tasks. Perform various initialization tasks.
''' '''
import locale, sys, os, re import locale, sys
# Default translation is NOOP # Default translation is NOOP
import __builtin__ import __builtin__
@ -109,96 +109,39 @@ if not _run_once:
except: except:
pass pass
# # local_open() opens a file that wont be inherited by child processes
def local_open(name, mode='r', bufsize=-1):
'''
Open a file that wont be inherited by child processes
Only supports the following modes:
r, w, a, rb, wb, ab, r+, w+, a+, r+b, w+b, a+b
'''
if iswindows: if iswindows:
class fwrapper(object): def local_open(name, mode='r', bufsize=-1):
mode += 'N'
def __init__(self, name, fobject): return open(name, mode, bufsize)
object.__setattr__(self, 'fobject', fobject) elif isosx:
object.__setattr__(self, 'name', name) import fcntl
FIOCLEX = 0x20006601
def __getattribute__(self, attr): def local_open(name, mode='r', bufsize=-1):
if attr in ('name', '__enter__', '__str__', '__unicode__', ans = open(name, mode, bufsize)
'__repr__', '__exit__'): try:
return object.__getattribute__(self, attr) fcntl.ioctl(ans.fileno(), FIOCLEX)
fobject = object.__getattribute__(self, 'fobject') except EnvironmentError:
return getattr(fobject, attr) fcntl.fcntl(ans, fcntl.F_SETFD, fcntl.fcntl(ans, fcntl.F_GETFD) | fcntl.FD_CLOEXEC)
return ans
def __setattr__(self, attr, val):
fobject = object.__getattribute__(self, 'fobject')
return setattr(fobject, attr, val)
def __repr__(self):
fobject = object.__getattribute__(self, 'fobject')
name = object.__getattribute__(self, 'name')
return re.sub(r'''['"]<fdopen>['"]''', repr(name),
repr(fobject))
def __str__(self):
return repr(self)
def __unicode__(self):
return repr(self).decode('utf-8')
def __enter__(self):
fobject = object.__getattribute__(self, 'fobject')
fobject.__enter__()
return self
def __exit__(self, *args):
fobject = object.__getattribute__(self, 'fobject')
return fobject.__exit__(*args)
m = mode[0]
random = len(mode) > 1 and mode[1] == '+'
binary = mode[-1] == 'b'
if m == 'a':
flags = os.O_APPEND| os.O_RDWR
flags |= os.O_RANDOM if random else os.O_SEQUENTIAL
elif m == 'r':
if random:
flags = os.O_RDWR | os.O_RANDOM
else:
flags = os.O_RDONLY | os.O_SEQUENTIAL
elif m == 'w':
if random:
flags = os.O_RDWR | os.O_RANDOM
else:
flags = os.O_WRONLY | os.O_SEQUENTIAL
flags |= os.O_TRUNC | os.O_CREAT
if binary:
flags |= os.O_BINARY
else:
flags |= os.O_TEXT
flags |= os.O_NOINHERIT
fd = os.open(name, flags)
ans = os.fdopen(fd, mode, bufsize)
ans = fwrapper(name, ans)
else: else:
import fcntl import fcntl
try: try:
cloexec_flag = fcntl.FD_CLOEXEC cloexec_flag = fcntl.FD_CLOEXEC
except AttributeError: except AttributeError:
cloexec_flag = 1 cloexec_flag = 1
# Python 2.x uses fopen which on recent glibc/linux kernel at least supports_mode_e = False
# respects the 'e' mode flag. On OS X the e is ignored. So to try def local_open(name, mode='r', bufsize=-1):
# to get atomicity where possible we pass 'e' and then only use global supports_mode_e
# fcntl only if CLOEXEC was not set.
if islinux:
mode += 'e' mode += 'e'
ans = open(name, mode, bufsize) ans = open(name, mode, bufsize)
if supports_mode_e:
return ans
old = fcntl.fcntl(ans, fcntl.F_GETFD) old = fcntl.fcntl(ans, fcntl.F_GETFD)
if not (old & cloexec_flag): if not (old & cloexec_flag):
fcntl.fcntl(ans, fcntl.F_SETFD, old | cloexec_flag) fcntl.fcntl(ans, fcntl.F_SETFD, old | cloexec_flag)
else:
supports_mode_e = True
return ans return ans
__builtin__.__dict__['lopen'] = local_open __builtin__.__dict__['lopen'] = local_open
@ -242,27 +185,43 @@ def test_lopen():
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from calibre import CurrentDir from calibre import CurrentDir
n = u'f\xe4llen' n = u'f\xe4llen'
print('testing lopen()')
with TemporaryDirectory() as tdir: if iswindows:
with CurrentDir(tdir): import msvcrt, win32api
with lopen(n, 'w') as f: def assert_not_inheritable(f):
if win32api.GetHandleInformation(msvcrt.get_osfhandle(f.fileno())) & 0b1:
raise SystemExit('File handle is inheritable!')
else:
def assert_not_inheritable(f):
if not fcntl.fcntl(f, fcntl.F_GETFD) & fcntl.FD_CLOEXEC:
raise SystemExit('File handle is inheritable!')
def copen(*args):
ans = lopen(*args)
assert_not_inheritable(ans)
return ans
with TemporaryDirectory() as tdir, CurrentDir(tdir):
with copen(n, 'w') as f:
f.write('one') f.write('one')
print 'O_CREAT tested' print 'O_CREAT tested'
with lopen(n, 'w+b') as f: with copen(n, 'w+b') as f:
f.write('two') f.write('two')
with lopen(n, 'r') as f: with copen(n, 'r') as f:
if f.read() == 'two': if f.read() == 'two':
print 'O_TRUNC tested' print 'O_TRUNC tested'
else: else:
raise Exception('O_TRUNC failed') raise Exception('O_TRUNC failed')
with lopen(n, 'ab') as f: with copen(n, 'ab') as f:
f.write('three') f.write('three')
with lopen(n, 'r+') as f: with copen(n, 'r+') as f:
if f.read() == 'twothree': if f.read() == 'twothree':
print 'O_APPEND tested' print 'O_APPEND tested'
else: else:
raise Exception('O_APPEND failed') raise Exception('O_APPEND failed')
with lopen(n, 'r+') as f: with copen(n, 'r+') as f:
f.seek(3) f.seek(3)
f.write('xxxxx') f.write('xxxxx')
f.seek(0) f.seek(0)
@ -270,6 +229,3 @@ def test_lopen():
print 'O_RANDOM tested' print 'O_RANDOM tested'
else: else:
raise Exception('O_RANDOM failed') raise Exception('O_RANDOM failed')