Skip to content

Commit d02c160

Browse files
committed
Fix "Bad file descriptor" error when unpickling an unnamed temporary file in a different process
1 parent 8e5e450 commit d02c160

File tree

1 file changed

+58
-6
lines changed

1 file changed

+58
-6
lines changed

dill/_dill.py

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -869,10 +869,28 @@ def _create_rlock(count, owner, *args): #XXX: ignores 'blocking'
869869
return lock
870870

871871
# thanks to matsjoyce for adding all the different file modes
872-
def _create_filehandle(name, mode, position, closed, open, strictio, fmode, fdata): # buffering=0
872+
def _create_filehandle(name, mode, position, closed, open, strictio, fmode, fdata, owner_id=None): # buffering=0
873873
# only pickles the handle, not the file contents... good? or StringIO(data)?
874874
# (for file contents see: http://effbot.org/librarybook/copy-reg.htm)
875875
# NOTE: handle special cases first (are there more special cases?)
876+
if owner_pid:
877+
# Handle unnamed temporary file in Linux
878+
#NOTE: Windows doesn't have it, other *nixes require C extensions.
879+
self_pid = os.getpid()
880+
self_id = (self_pid, _process_create_time(self_pid))
881+
if owner_id != self_id:
882+
# If the fd is from this process, go on, else:
883+
owner_pid = owner_id[0]
884+
if owner_id[1] == _process_create_time(owner_pid):
885+
# Creation times are equal or both are None.
886+
# The fd is likely from a process in this machine.
887+
name = '/proc/%d/fd/%s' % (owner_pid, name)
888+
if not os.path.exists(name):
889+
name = '<fdopen>'
890+
else:
891+
# The fd is from a finished process or not from this machine.
892+
name = '<fdopen>'
893+
876894
names = {'<stdin>':sys.__stdin__, '<stdout>':sys.__stdout__,
877895
'<stderr>':sys.__stderr__} #XXX: better fileno=(0,1,2) ?
878896
if name in list(names.keys()):
@@ -888,7 +906,7 @@ def _create_filehandle(name, mode, position, closed, open, strictio, fmode, fdat
888906
raise ValueError("invalid mode: '%s'" % mode)
889907
try:
890908
exists = os.path.exists(name)
891-
except:
909+
except Exception:
892910
exists = False
893911
if not exists:
894912
if strictio:
@@ -1407,6 +1425,39 @@ def save_attrgetter(pickler, obj):
14071425
log.info("# Ag")
14081426
return
14091427

1428+
def _process_create_time(pid):
1429+
"""process creation time in seconds since boot"""
1430+
try:
1431+
return _process_create_time.cache[pid]
1432+
except KeyError:
1433+
pass
1434+
try:
1435+
import math
1436+
ticks_per_second = os.sysconf('SC_CLK_TCK')
1437+
ndigits = math.ceil(math.log10(ticks_per_second))
1438+
try:
1439+
from psutil import Process
1440+
psutil_time = psutil.Process(pid).create_time()
1441+
# psutil adds the boot_time to create_time.
1442+
# We don't care (and it could potentially drift with NTP).
1443+
create_time = create_time - psutil.boot_time()
1444+
except ImportError:
1445+
with open('/proc/%d/stat' % pid) as file:
1446+
stat = file.read()
1447+
# based on psutil and man proc
1448+
fields = stat.rpartition(')')[2].split()
1449+
starttime = float(fields[19])
1450+
create_time = starttime / ticks_per_second
1451+
except Exception as error:
1452+
log.debug("_process_create_time() exception: %s", error)
1453+
create_time = None
1454+
if create_time:
1455+
# Deal with float imprecision and time drift, we don't need full precision.
1456+
create_time = math.trunc(round(create_time, ndigits))
1457+
_process_create_time.cache[pid] = create_time
1458+
return create_time
1459+
_process_create_time.cache = {}
1460+
14101461
def _save_file(pickler, obj, open_):
14111462
if obj.closed:
14121463
position = 0
@@ -1428,12 +1479,13 @@ def _save_file(pickler, obj, open_):
14281479
else:
14291480
strictio = False
14301481
fmode = 0 # HANDLE_FMODE
1431-
pickler.save_reduce(_create_filehandle, (obj.name, obj.mode, position,
1432-
obj.closed, open_, strictio,
1433-
fmode, fdata), obj=obj)
1482+
args = (obj.name, obj.mode, position, obj.closed, open_, strictio, fmode, fdata)
1483+
if isinstance(obj.name, int):
1484+
pid = os.getpid()
1485+
args += (pid, _process_create_time(pid))
1486+
pickler.save_reduce(_create_filehandle, args, obj=obj)
14341487
return
14351488

1436-
14371489
@register(FileType) #XXX: in 3.x has buffer=0, needs different _create?
14381490
@register(BufferedRandomType)
14391491
@register(BufferedReaderType)

0 commit comments

Comments
 (0)