Merge "Handle non-blocking renames during object PUTs"
This commit is contained in:
commit
35601d4191
@ -36,3 +36,7 @@ class AlreadyExistsAsDir(SwiftOnFileFsException):
|
|||||||
|
|
||||||
class AlreadyExistsAsFile(SwiftOnFileFsException):
|
class AlreadyExistsAsFile(SwiftOnFileFsException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DiskFileContainerDoesNotExist(SwiftOnFileFsException):
|
||||||
|
pass
|
||||||
|
@ -27,7 +27,7 @@ from uuid import uuid4
|
|||||||
from eventlet import sleep
|
from eventlet import sleep
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from swiftonfile.swift.common.exceptions import AlreadyExistsAsFile, \
|
from swiftonfile.swift.common.exceptions import AlreadyExistsAsFile, \
|
||||||
AlreadyExistsAsDir
|
AlreadyExistsAsDir, DiskFileContainerDoesNotExist
|
||||||
from swift.common.utils import ThreadPool, hash_path, \
|
from swift.common.utils import ThreadPool, hash_path, \
|
||||||
normalize_timestamp, fallocate, Timestamp
|
normalize_timestamp, fallocate, Timestamp
|
||||||
from swift.common.exceptions import DiskFileNotExist, DiskFileError, \
|
from swift.common.exceptions import DiskFileNotExist, DiskFileError, \
|
||||||
@ -320,8 +320,13 @@ class DiskFileWriter(object):
|
|||||||
try:
|
try:
|
||||||
do_rename(self._tmppath, df._data_file)
|
do_rename(self._tmppath, df._data_file)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
if err.errno in (errno.ENOENT, errno.EIO) \
|
if err.errno in (errno.ENOENT, errno.EIO,
|
||||||
|
errno.EBUSY, errno.ESTALE) \
|
||||||
and attempts < MAX_RENAME_ATTEMPTS:
|
and attempts < MAX_RENAME_ATTEMPTS:
|
||||||
|
# Some versions of GlusterFS had rename() as non-blocking
|
||||||
|
# operation. So we check for STALE and EBUSY. This was
|
||||||
|
# fixed recently: http://review.gluster.org/#/c/13366/
|
||||||
|
# The comment that follows is for ENOENT and EIO...
|
||||||
# FIXME: Why either of these two error conditions is
|
# FIXME: Why either of these two error conditions is
|
||||||
# happening is unknown at this point. This might be a
|
# happening is unknown at this point. This might be a
|
||||||
# FUSE issue of some sort or a possible race
|
# FUSE issue of some sort or a possible race
|
||||||
@ -411,7 +416,7 @@ class DiskFileWriter(object):
|
|||||||
|
|
||||||
df._threadpool.force_run_in_thread(self._finalize_put, metadata)
|
df._threadpool.force_run_in_thread(self._finalize_put, metadata)
|
||||||
|
|
||||||
# Avoid the unlink() system call as part of the mkstemp context
|
# Avoid the unlink() system call as part of the create context
|
||||||
# cleanup
|
# cleanup
|
||||||
self._tmppath = None
|
self._tmppath = None
|
||||||
|
|
||||||
@ -579,6 +584,8 @@ class DiskFile(object):
|
|||||||
self._is_dir = False
|
self._is_dir = False
|
||||||
self._metadata = None
|
self._metadata = None
|
||||||
self._fd = None
|
self._fd = None
|
||||||
|
# This fd attribute is not used in PUT path. fd used in PUT path
|
||||||
|
# is encapsulated inside DiskFileWriter object.
|
||||||
self._stat = None
|
self._stat = None
|
||||||
# Don't store a value for data_file until we know it exists.
|
# Don't store a value for data_file until we know it exists.
|
||||||
self._data_file = None
|
self._data_file = None
|
||||||
@ -884,6 +891,7 @@ class DiskFile(object):
|
|||||||
|
|
||||||
# Assume the full directory path exists to the file already, and
|
# Assume the full directory path exists to the file already, and
|
||||||
# construct the proper name for the temporary file.
|
# construct the proper name for the temporary file.
|
||||||
|
fd = None
|
||||||
attempts = 1
|
attempts = 1
|
||||||
while True:
|
while True:
|
||||||
# To know more about why following temp file naming convention is
|
# To know more about why following temp file naming convention is
|
||||||
@ -911,7 +919,7 @@ class DiskFile(object):
|
|||||||
if attempts >= MAX_OPEN_ATTEMPTS:
|
if attempts >= MAX_OPEN_ATTEMPTS:
|
||||||
# We failed after N attempts to create the temporary
|
# We failed after N attempts to create the temporary
|
||||||
# file.
|
# file.
|
||||||
raise DiskFileError('DiskFile.mkstemp(): failed to'
|
raise DiskFileError('DiskFile.create(): failed to'
|
||||||
' successfully create a temporary file'
|
' successfully create a temporary file'
|
||||||
' without running into a name conflict'
|
' without running into a name conflict'
|
||||||
' after %d of %d attempts for: %s' % (
|
' after %d of %d attempts for: %s' % (
|
||||||
@ -924,24 +932,41 @@ class DiskFile(object):
|
|||||||
# FIXME: Possible FUSE issue or race condition, let's
|
# FIXME: Possible FUSE issue or race condition, let's
|
||||||
# sleep on it and retry the operation.
|
# sleep on it and retry the operation.
|
||||||
_random_sleep()
|
_random_sleep()
|
||||||
logging.warn("DiskFile.mkstemp(): %s ... retrying in"
|
logging.warn("DiskFile.create(): %s ... retrying in"
|
||||||
" 0.1 secs", gerr)
|
" 0.1 secs", gerr)
|
||||||
attempts += 1
|
attempts += 1
|
||||||
elif not self._obj_path:
|
elif not self._obj_path:
|
||||||
|
# ENOENT
|
||||||
# No directory hierarchy and the create failed telling us
|
# No directory hierarchy and the create failed telling us
|
||||||
# the container or volume directory does not exist. This
|
# the container or volume directory does not exist. This
|
||||||
# could be a FUSE issue or some race condition, so let's
|
# could be a FUSE issue or some race condition, so let's
|
||||||
# sleep a bit and retry.
|
# sleep a bit and retry.
|
||||||
|
# Handle race:
|
||||||
|
# This can be the issue when memcache has cached that the
|
||||||
|
# container exists. If someone removes the container dir
|
||||||
|
# from filesystem, it's not reflected in memcache. So
|
||||||
|
# swift reports that the container exists and this code
|
||||||
|
# tries to create a file in a directory that does not
|
||||||
|
# exist. However, it's wrong to create the container here.
|
||||||
_random_sleep()
|
_random_sleep()
|
||||||
logging.warn("DiskFile.mkstemp(): %s ... retrying in"
|
logging.warn("DiskFile.create(): %s ... retrying in"
|
||||||
" 0.1 secs", gerr)
|
" 0.1 secs", gerr)
|
||||||
attempts += 1
|
attempts += 1
|
||||||
|
if attempts > 2:
|
||||||
|
# Ideally we would want to return 404 indicating that
|
||||||
|
# the container itself does not exist. Can't be done
|
||||||
|
# though as the caller won't catch DiskFileNotExist.
|
||||||
|
# We raise an exception with a meaningful name for
|
||||||
|
# correctness.
|
||||||
|
logging.warn("Container dir %s does not exist",
|
||||||
|
self._container_path)
|
||||||
|
raise DiskFileContainerDoesNotExist
|
||||||
elif attempts > 1:
|
elif attempts > 1:
|
||||||
# Got ENOENT after previously making the path. This could
|
# Got ENOENT after previously making the path. This could
|
||||||
# also be a FUSE issue or some race condition, nap and
|
# also be a FUSE issue or some race condition, nap and
|
||||||
# retry.
|
# retry.
|
||||||
_random_sleep()
|
_random_sleep()
|
||||||
logging.warn("DiskFile.mkstemp(): %s ... retrying in"
|
logging.warn("DiskFile.create(): %s ... retrying in"
|
||||||
" 0.1 secs" % gerr)
|
" 0.1 secs" % gerr)
|
||||||
attempts += 1
|
attempts += 1
|
||||||
else:
|
else:
|
||||||
@ -963,6 +988,8 @@ class DiskFile(object):
|
|||||||
# Ensure it is properly owned before we make it available.
|
# Ensure it is properly owned before we make it available.
|
||||||
do_fchown(fd, self._uid, self._gid)
|
do_fchown(fd, self._uid, self._gid)
|
||||||
dw = DiskFileWriter(fd, tmppath, self)
|
dw = DiskFileWriter(fd, tmppath, self)
|
||||||
|
# It's now the responsibility of DiskFileWriter to close this fd.
|
||||||
|
fd = None
|
||||||
yield dw
|
yield dw
|
||||||
finally:
|
finally:
|
||||||
if dw:
|
if dw:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user