Issue #14099: Writing to ZipFile and reading multiple ZipExtFiles is

threadsafe now.
This commit is contained in:
Serhiy Storchaka 2015-01-26 13:53:38 +02:00
parent b67c516d8b
commit f15e524026
2 changed files with 209 additions and 195 deletions

View file

@ -13,6 +13,7 @@ import stat
import shutil import shutil
import struct import struct
import binascii import binascii
import threading
try: try:
@ -647,12 +648,14 @@ def _get_decompressor(compress_type):
class _SharedFile: class _SharedFile:
def __init__(self, file, pos, close): def __init__(self, file, pos, close, lock):
self._file = file self._file = file
self._pos = pos self._pos = pos
self._close = close self._close = close
self._lock = lock
def read(self, n=-1): def read(self, n=-1):
with self._lock:
self._file.seek(self._pos) self._file.seek(self._pos)
data = self._file.read(n) data = self._file.read(n)
self._pos = self._file.tell() self._pos = self._file.tell()
@ -990,6 +993,7 @@ class ZipFile:
self.fp = file self.fp = file
self.filename = getattr(file, 'name', None) self.filename = getattr(file, 'name', None)
self._fileRefCnt = 1 self._fileRefCnt = 1
self._lock = threading.RLock()
try: try:
if mode == 'r': if mode == 'r':
@ -1214,7 +1218,7 @@ class ZipFile:
zinfo = self.getinfo(name) zinfo = self.getinfo(name)
self._fileRefCnt += 1 self._fileRefCnt += 1
zef_file = _SharedFile(self.fp, zinfo.header_offset, self._fpclose) zef_file = _SharedFile(self.fp, zinfo.header_offset, self._fpclose, self._lock)
try: try:
# Skip the file header: # Skip the file header:
fheader = zef_file.read(sizeFileHeader) fheader = zef_file.read(sizeFileHeader)
@ -1410,6 +1414,7 @@ class ZipFile:
zinfo.file_size = st.st_size zinfo.file_size = st.st_size
zinfo.flag_bits = 0x00 zinfo.flag_bits = 0x00
with self._lock:
self.fp.seek(self.start_dir, 0) self.fp.seek(self.start_dir, 0)
zinfo.header_offset = self.fp.tell() # Start of header bytes zinfo.header_offset = self.fp.tell() # Start of header bytes
if zinfo.compress_type == ZIP_LZMA: if zinfo.compress_type == ZIP_LZMA:
@ -1498,6 +1503,7 @@ class ZipFile:
"Attempt to write to ZIP archive that was already closed") "Attempt to write to ZIP archive that was already closed")
zinfo.file_size = len(data) # Uncompressed size zinfo.file_size = len(data) # Uncompressed size
with self._lock:
self.fp.seek(self.start_dir, 0) self.fp.seek(self.start_dir, 0)
zinfo.header_offset = self.fp.tell() # Start of header data zinfo.header_offset = self.fp.tell() # Start of header data
if compress_type is not None: if compress_type is not None:
@ -1543,6 +1549,15 @@ class ZipFile:
try: try:
if self.mode in ("w", "a") and self._didModify: # write ending records if self.mode in ("w", "a") and self._didModify: # write ending records
with self._lock:
self.fp.seek(self.start_dir, 0)
self._write_end_record()
finally:
fp = self.fp
self.fp = None
self._fpclose(fp)
def _write_end_record(self):
self.fp.seek(self.start_dir, 0) self.fp.seek(self.start_dir, 0)
for zinfo in self.filelist: # write central directory for zinfo in self.filelist: # write central directory
dt = zinfo.date_time dt = zinfo.date_time
@ -1643,10 +1658,6 @@ class ZipFile:
self.fp.write(endrec) self.fp.write(endrec)
self.fp.write(self._comment) self.fp.write(self._comment)
self.fp.flush() self.fp.flush()
finally:
fp = self.fp
self.fp = None
self._fpclose(fp)
def _fpclose(self, fp): def _fpclose(self, fp):
assert self._fileRefCnt > 0 assert self._fileRefCnt > 0

View file

@ -218,6 +218,9 @@ Core and Builtins
Library Library
------- -------
- Issue #14099: Writing to ZipFile and reading multiple ZipExtFiles is
threadsafe now.
- Issue #19361: JSON decoder now raises JSONDecodeError instead of ValueError. - Issue #19361: JSON decoder now raises JSONDecodeError instead of ValueError.
- Issue #18518: timeit now rejects statements which can't be compiled outside - Issue #18518: timeit now rejects statements which can't be compiled outside