Avoid unnecessary unlink() on every successful PUT
If you do a strace on object-server PUT operation, you'd see that there's an unlink() sys call which _always_ fails with ENOENT. mkstemp() creates a temp file which is renamed later to it's final object path in filesystem. Hence, on a successful object PUT, the tempfile would never exist in its original location after rename. Change-Id: I805c7c200107e2d56278f0fb35692a51cb1edc0b Signed-off-by: Prashanth Pai <ppai@redhat.com>
This commit is contained in:
parent
682f660d98
commit
42c790d04b
@ -806,6 +806,11 @@ class DiskFileWriter(object):
|
|||||||
self._upload_size = 0
|
self._upload_size = 0
|
||||||
self._last_sync = 0
|
self._last_sync = 0
|
||||||
self._extension = '.data'
|
self._extension = '.data'
|
||||||
|
self._put_succeeded = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def put_succeeded(self):
|
||||||
|
return self._put_succeeded
|
||||||
|
|
||||||
def write(self, chunk):
|
def write(self, chunk):
|
||||||
"""
|
"""
|
||||||
@ -852,6 +857,10 @@ class DiskFileWriter(object):
|
|||||||
# After the rename completes, this object will be available for other
|
# After the rename completes, this object will be available for other
|
||||||
# requests to reference.
|
# requests to reference.
|
||||||
renamer(self._tmppath, target_path)
|
renamer(self._tmppath, target_path)
|
||||||
|
# If rename is successful, flag put as succeeded. This is done to avoid
|
||||||
|
# unnecessary os.unlink() of tempfile later. As renamer() has
|
||||||
|
# succeeded, the tempfile would no longer exist at its original path.
|
||||||
|
self._put_succeeded = True
|
||||||
try:
|
try:
|
||||||
hash_cleanup_listdir(self._datadir)
|
hash_cleanup_listdir(self._datadir)
|
||||||
except OSError:
|
except OSError:
|
||||||
@ -1595,23 +1604,31 @@ class DiskFile(object):
|
|||||||
if not exists(self._tmpdir):
|
if not exists(self._tmpdir):
|
||||||
mkdirs(self._tmpdir)
|
mkdirs(self._tmpdir)
|
||||||
fd, tmppath = mkstemp(dir=self._tmpdir)
|
fd, tmppath = mkstemp(dir=self._tmpdir)
|
||||||
|
dfw = None
|
||||||
try:
|
try:
|
||||||
if size is not None and size > 0:
|
if size is not None and size > 0:
|
||||||
try:
|
try:
|
||||||
fallocate(fd, size)
|
fallocate(fd, size)
|
||||||
except OSError:
|
except OSError:
|
||||||
raise DiskFileNoSpace()
|
raise DiskFileNoSpace()
|
||||||
yield DiskFileWriter(self._name, self._datadir, fd, tmppath,
|
dfw = DiskFileWriter(self._name, self._datadir, fd, tmppath,
|
||||||
self._bytes_per_sync, self._threadpool)
|
self._bytes_per_sync, self._threadpool)
|
||||||
|
yield dfw
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
if (dfw is None) or (not dfw.put_succeeded):
|
||||||
|
# Try removing the temp file only if put did NOT succeed.
|
||||||
|
#
|
||||||
|
# dfw.put_succeeded is set to True after renamer() succeeds in
|
||||||
|
# DiskFileWriter._finalize_put()
|
||||||
try:
|
try:
|
||||||
os.unlink(tmppath)
|
os.unlink(tmppath)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
self._logger.exception('Error removing tempfile: %s' %
|
||||||
|
tmppath)
|
||||||
|
|
||||||
def write_metadata(self, metadata):
|
def write_metadata(self, metadata):
|
||||||
"""
|
"""
|
||||||
|
@ -2370,5 +2370,74 @@ class TestDiskFile(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
self.fail('`splice` not called with expected arguments')
|
self.fail('`splice` not called with expected arguments')
|
||||||
|
|
||||||
|
def test_create_unlink_cleanup_DiskFileNoSpace(self):
|
||||||
|
# Test cleanup when DiskFileNoSpace() is raised.
|
||||||
|
df = self.df_mgr.get_diskfile(self.existing_device, '0', 'abc', '123',
|
||||||
|
'xyz')
|
||||||
|
_m_fallocate = mock.MagicMock(side_effect=OSError(errno.ENOSPC,
|
||||||
|
os.strerror(errno.ENOSPC)))
|
||||||
|
_m_unlink = mock.Mock()
|
||||||
|
with mock.patch("swift.obj.diskfile.fallocate", _m_fallocate):
|
||||||
|
with mock.patch("os.unlink", _m_unlink):
|
||||||
|
try:
|
||||||
|
with df.create(size=100):
|
||||||
|
pass
|
||||||
|
except DiskFileNoSpace:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fail("Expected exception DiskFileNoSpace")
|
||||||
|
self.assertTrue(_m_fallocate.called)
|
||||||
|
self.assertTrue(_m_unlink.called)
|
||||||
|
self.assert_(len(self.df_mgr.logger.log_dict['exception']) == 0)
|
||||||
|
|
||||||
|
def test_create_unlink_cleanup_renamer_fails(self):
|
||||||
|
# Test cleanup when renamer fails
|
||||||
|
_m_renamer = mock.MagicMock(side_effect=OSError(errno.ENOENT,
|
||||||
|
os.strerror(errno.ENOENT)))
|
||||||
|
_m_unlink = mock.Mock()
|
||||||
|
df = self._simple_get_diskfile()
|
||||||
|
data = '0' * 100
|
||||||
|
metadata = {
|
||||||
|
'ETag': md5(data).hexdigest(),
|
||||||
|
'X-Timestamp': Timestamp(time()).internal,
|
||||||
|
'Content-Length': str(100),
|
||||||
|
}
|
||||||
|
with mock.patch("swift.obj.diskfile.renamer", _m_renamer):
|
||||||
|
with mock.patch("os.unlink", _m_unlink):
|
||||||
|
try:
|
||||||
|
with df.create(size=100) as writer:
|
||||||
|
writer.write(data)
|
||||||
|
writer.put(metadata)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fail("Expected OSError exception")
|
||||||
|
self.assertFalse(writer.put_succeeded)
|
||||||
|
self.assertTrue(_m_renamer.called)
|
||||||
|
self.assertTrue(_m_unlink.called)
|
||||||
|
self.assert_(len(self.df_mgr.logger.log_dict['exception']) == 0)
|
||||||
|
|
||||||
|
def test_create_unlink_cleanup_logging(self):
|
||||||
|
# Test logging of os.unlink() failures.
|
||||||
|
df = self.df_mgr.get_diskfile(self.existing_device, '0', 'abc', '123',
|
||||||
|
'xyz')
|
||||||
|
_m_fallocate = mock.MagicMock(side_effect=OSError(errno.ENOSPC,
|
||||||
|
os.strerror(errno.ENOSPC)))
|
||||||
|
_m_unlink = mock.MagicMock(side_effect=OSError(errno.ENOENT,
|
||||||
|
os.strerror(errno.ENOENT)))
|
||||||
|
with mock.patch("swift.obj.diskfile.fallocate", _m_fallocate):
|
||||||
|
with mock.patch("os.unlink", _m_unlink):
|
||||||
|
try:
|
||||||
|
with df.create(size=100):
|
||||||
|
pass
|
||||||
|
except DiskFileNoSpace:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.fail("Expected exception DiskFileNoSpace")
|
||||||
|
self.assertTrue(_m_fallocate.called)
|
||||||
|
self.assertTrue(_m_unlink.called)
|
||||||
|
self.assert_(self.df_mgr.logger.log_dict['exception'][0][0][0].
|
||||||
|
startswith("Error removing tempfile:"))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user