Merge "make obj replicator locking more optimistic"
This commit is contained in:
commit
3f01f889d5
@ -166,50 +166,59 @@ def get_hashes(partition_dir, recalculate=[], do_listdir=False,
|
|||||||
|
|
||||||
hashed = 0
|
hashed = 0
|
||||||
hashes_file = join(partition_dir, HASH_FILE)
|
hashes_file = join(partition_dir, HASH_FILE)
|
||||||
with lock_path(partition_dir):
|
modified = False
|
||||||
modified = False
|
hashes = {}
|
||||||
hashes = {}
|
mtime = -1
|
||||||
try:
|
try:
|
||||||
with open(hashes_file, 'rb') as fp:
|
with open(hashes_file, 'rb') as fp:
|
||||||
hashes = pickle.load(fp)
|
hashes = pickle.load(fp)
|
||||||
except Exception:
|
mtime = os.path.getmtime(hashes_file)
|
||||||
do_listdir = True
|
except Exception:
|
||||||
if do_listdir:
|
do_listdir = True
|
||||||
hashes = dict(((suff, hashes.get(suff, None))
|
if do_listdir:
|
||||||
for suff in os.listdir(partition_dir)
|
for suff in os.listdir(partition_dir):
|
||||||
if len(suff) == 3 and isdir(join(partition_dir, suff))))
|
if len(suff) == 3 and isdir(join(partition_dir, suff)):
|
||||||
|
hashes.setdefault(suff, None)
|
||||||
|
modified = True
|
||||||
|
hashes.update((hash_, None) for hash_ in recalculate)
|
||||||
|
for suffix, hash_ in hashes.items():
|
||||||
|
if not hash_:
|
||||||
|
suffix_dir = join(partition_dir, suffix)
|
||||||
|
if isdir(suffix_dir):
|
||||||
|
try:
|
||||||
|
hashes[suffix] = hash_suffix(suffix_dir, reclaim_age)
|
||||||
|
hashed += 1
|
||||||
|
except OSError:
|
||||||
|
logging.exception(_('Error hashing suffix'))
|
||||||
|
else:
|
||||||
|
del hashes[suffix]
|
||||||
modified = True
|
modified = True
|
||||||
for hash_ in recalculate:
|
if modified:
|
||||||
hashes[hash_] = None
|
with lock_path(partition_dir):
|
||||||
for suffix, hash_ in hashes.items():
|
if not os.path.exists(hashes_file) or \
|
||||||
if not hash_:
|
os.path.getmtime(hashes_file) == mtime:
|
||||||
suffix_dir = join(partition_dir, suffix)
|
write_pickle(
|
||||||
if os.path.exists(suffix_dir):
|
hashes, hashes_file, partition_dir, PICKLE_PROTOCOL)
|
||||||
try:
|
return hashed, hashes
|
||||||
hashes[suffix] = hash_suffix(suffix_dir, reclaim_age)
|
return get_hashes(partition_dir, recalculate, do_listdir,
|
||||||
hashed += 1
|
reclaim_age)
|
||||||
except OSError:
|
else:
|
||||||
logging.exception(_('Error hashing suffix'))
|
|
||||||
hashes[suffix] = None
|
|
||||||
else:
|
|
||||||
del hashes[suffix]
|
|
||||||
modified = True
|
|
||||||
sleep()
|
|
||||||
if modified:
|
|
||||||
write_pickle(hashes, hashes_file, partition_dir, PICKLE_PROTOCOL)
|
|
||||||
return hashed, hashes
|
return hashed, hashes
|
||||||
|
|
||||||
|
|
||||||
def tpooled_get_hashes(*args, **kwargs):
|
def tpool_reraise(func, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Hack to work around Eventlet's tpool not catching and reraising Timeouts.
|
Hack to work around Eventlet's tpool not catching and reraising Timeouts.
|
||||||
We return the Timeout, Timeout if it's raised, the caller looks for it
|
|
||||||
and reraises it if found.
|
|
||||||
"""
|
"""
|
||||||
try:
|
def inner():
|
||||||
return get_hashes(*args, **kwargs)
|
try:
|
||||||
except Timeout, err:
|
return func(*args, **kwargs)
|
||||||
return err, err
|
except BaseException, err:
|
||||||
|
return err
|
||||||
|
resp = tpool.execute(inner)
|
||||||
|
if isinstance(resp, BaseException):
|
||||||
|
raise resp
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
class ObjectReplicator(Daemon):
|
class ObjectReplicator(Daemon):
|
||||||
@ -392,12 +401,9 @@ class ObjectReplicator(Daemon):
|
|||||||
self.logger.increment('partition.update.count.%s' % (job['device'],))
|
self.logger.increment('partition.update.count.%s' % (job['device'],))
|
||||||
begin = time.time()
|
begin = time.time()
|
||||||
try:
|
try:
|
||||||
hashed, local_hash = tpool.execute(tpooled_get_hashes, job['path'],
|
hashed, local_hash = tpool_reraise(get_hashes, job['path'],
|
||||||
do_listdir=(self.replication_count % 10) == 0,
|
do_listdir=(self.replication_count % 10) == 0,
|
||||||
reclaim_age=self.reclaim_age)
|
reclaim_age=self.reclaim_age)
|
||||||
# See tpooled_get_hashes "Hack".
|
|
||||||
if isinstance(hashed, BaseException):
|
|
||||||
raise hashed
|
|
||||||
self.suffix_hash += hashed
|
self.suffix_hash += hashed
|
||||||
self.logger.update_stats('suffix.hashes', hashed)
|
self.logger.update_stats('suffix.hashes', hashed)
|
||||||
attempts_left = len(job['nodes'])
|
attempts_left = len(job['nodes'])
|
||||||
@ -428,12 +434,9 @@ class ObjectReplicator(Daemon):
|
|||||||
local_hash[suffix] != remote_hash.get(suffix, -1)]
|
local_hash[suffix] != remote_hash.get(suffix, -1)]
|
||||||
if not suffixes:
|
if not suffixes:
|
||||||
continue
|
continue
|
||||||
hashed, recalc_hash = tpool.execute(tpooled_get_hashes,
|
hashed, recalc_hash = tpool_reraise(get_hashes,
|
||||||
job['path'], recalculate=suffixes,
|
job['path'], recalculate=suffixes,
|
||||||
reclaim_age=self.reclaim_age)
|
reclaim_age=self.reclaim_age)
|
||||||
# See tpooled_get_hashes "Hack".
|
|
||||||
if isinstance(hashed, BaseException):
|
|
||||||
raise hashed
|
|
||||||
self.logger.update_stats('suffix.hashes', hashed)
|
self.logger.update_stats('suffix.hashes', hashed)
|
||||||
local_hash = recalc_hash
|
local_hash = recalc_hash
|
||||||
suffixes = [suffix for suffix in local_hash if
|
suffixes = [suffix for suffix in local_hash if
|
||||||
|
@ -44,8 +44,8 @@ from swift.common.constraints import check_object_creation, check_mount, \
|
|||||||
check_float, check_utf8
|
check_float, check_utf8
|
||||||
from swift.common.exceptions import ConnectionTimeout, DiskFileError, \
|
from swift.common.exceptions import ConnectionTimeout, DiskFileError, \
|
||||||
DiskFileNotExist
|
DiskFileNotExist
|
||||||
from swift.obj.replicator import tpooled_get_hashes, invalidate_hash, \
|
from swift.obj.replicator import tpool_reraise, invalidate_hash, \
|
||||||
quarantine_renamer
|
quarantine_renamer, get_hashes
|
||||||
from swift.common.http import is_success, HTTPInsufficientStorage, \
|
from swift.common.http import is_success, HTTPInsufficientStorage, \
|
||||||
HTTPClientDisconnect
|
HTTPClientDisconnect
|
||||||
|
|
||||||
@ -865,12 +865,7 @@ class ObjectController(object):
|
|||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
mkdirs(path)
|
mkdirs(path)
|
||||||
suffixes = suffix.split('-') if suffix else []
|
suffixes = suffix.split('-') if suffix else []
|
||||||
_junk, hashes = tpool.execute(tpooled_get_hashes, path,
|
_junk, hashes = tpool_reraise(get_hashes, path, recalculate=suffixes)
|
||||||
recalculate=suffixes)
|
|
||||||
# See tpooled_get_hashes "Hack".
|
|
||||||
if isinstance(hashes, BaseException):
|
|
||||||
self.logger.increment('REPLICATE.errors')
|
|
||||||
raise hashes
|
|
||||||
self.logger.timing_since('REPLICATE.timing', start_time)
|
self.logger.timing_since('REPLICATE.timing', start_time)
|
||||||
return Response(body=pickle.dumps(hashes))
|
return Response(body=pickle.dumps(hashes))
|
||||||
|
|
||||||
|
@ -234,3 +234,25 @@ class MockTrue(object):
|
|||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return other is not True
|
return other is not True
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def mock(update):
|
||||||
|
returns = []
|
||||||
|
deletes = []
|
||||||
|
for key, value in update.items():
|
||||||
|
imports = key.split('.')
|
||||||
|
attr = imports.pop(-1)
|
||||||
|
module = __import__(imports[0], fromlist=imports[1:])
|
||||||
|
for modname in imports[1:]:
|
||||||
|
module = getattr(module, modname)
|
||||||
|
if hasattr(module, attr):
|
||||||
|
returns.append((module, attr, getattr(module, attr)))
|
||||||
|
else:
|
||||||
|
deletes.append((module, attr))
|
||||||
|
setattr(module, attr, value)
|
||||||
|
yield True
|
||||||
|
for module, attr, value in returns:
|
||||||
|
setattr(module, attr, value)
|
||||||
|
for module, attr in deletes:
|
||||||
|
delattr(module, attr)
|
||||||
|
@ -27,7 +27,7 @@ import tempfile
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from eventlet.green import subprocess
|
from eventlet.green import subprocess
|
||||||
from eventlet import Timeout, tpool
|
from eventlet import Timeout, tpool
|
||||||
from test.unit import FakeLogger
|
from test.unit import FakeLogger, mock
|
||||||
from swift.common import utils
|
from swift.common import utils
|
||||||
from swift.common.utils import hash_path, mkdirs, normalize_timestamp
|
from swift.common.utils import hash_path, mkdirs, normalize_timestamp
|
||||||
from swift.common import ring
|
from swift.common import ring
|
||||||
@ -209,6 +209,41 @@ class TestObjectReplicator(unittest.TestCase):
|
|||||||
self.assertEquals(hashed, 1)
|
self.assertEquals(hashed, 1)
|
||||||
self.assert_('a83' in hashes)
|
self.assert_('a83' in hashes)
|
||||||
|
|
||||||
|
def test_get_hashes_unmodified(self):
|
||||||
|
df = DiskFile(self.devices, 'sda', '0', 'a', 'c', 'o', FakeLogger())
|
||||||
|
mkdirs(df.datadir)
|
||||||
|
with open(os.path.join(df.datadir, normalize_timestamp(
|
||||||
|
time.time()) + '.ts'), 'wb') as f:
|
||||||
|
f.write('1234567890')
|
||||||
|
part = os.path.join(self.objects, '0')
|
||||||
|
hashed, hashes = object_replicator.get_hashes(part)
|
||||||
|
i = [0]
|
||||||
|
def getmtime(filename):
|
||||||
|
i[0] += 1
|
||||||
|
return 1
|
||||||
|
with mock({'os.path.getmtime': getmtime}):
|
||||||
|
hashed, hashes = object_replicator.get_hashes(
|
||||||
|
part, recalculate=['a83'])
|
||||||
|
self.assertEquals(i[0], 2)
|
||||||
|
|
||||||
|
def test_get_hashes_modified(self):
|
||||||
|
df = DiskFile(self.devices, 'sda', '0', 'a', 'c', 'o', FakeLogger())
|
||||||
|
mkdirs(df.datadir)
|
||||||
|
with open(os.path.join(df.datadir, normalize_timestamp(
|
||||||
|
time.time()) + '.ts'), 'wb') as f:
|
||||||
|
f.write('1234567890')
|
||||||
|
part = os.path.join(self.objects, '0')
|
||||||
|
hashed, hashes = object_replicator.get_hashes(part)
|
||||||
|
i = [0]
|
||||||
|
def getmtime(filename):
|
||||||
|
if i[0] < 3:
|
||||||
|
i[0] += 1
|
||||||
|
return i[0]
|
||||||
|
with mock({'os.path.getmtime': getmtime}):
|
||||||
|
hashed, hashes = object_replicator.get_hashes(
|
||||||
|
part, recalculate=['a83'])
|
||||||
|
self.assertEquals(i[0], 3)
|
||||||
|
|
||||||
def test_hash_suffix_hash_dir_is_file_quarantine(self):
|
def test_hash_suffix_hash_dir_is_file_quarantine(self):
|
||||||
df = DiskFile(self.devices, 'sda', '0', 'a', 'c', 'o', FakeLogger())
|
df = DiskFile(self.devices, 'sda', '0', 'a', 'c', 'o', FakeLogger())
|
||||||
mkdirs(os.path.dirname(df.datadir))
|
mkdirs(os.path.dirname(df.datadir))
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
|
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import unittest
|
import unittest
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
@ -37,7 +35,6 @@ from swift.common import utils
|
|||||||
from swift.common.utils import hash_path, mkdirs, normalize_timestamp, \
|
from swift.common.utils import hash_path, mkdirs, normalize_timestamp, \
|
||||||
NullLogger, storage_directory
|
NullLogger, storage_directory
|
||||||
from swift.common.exceptions import DiskFileNotExist
|
from swift.common.exceptions import DiskFileNotExist
|
||||||
from swift.obj import replicator
|
|
||||||
from eventlet import tpool
|
from eventlet import tpool
|
||||||
|
|
||||||
|
|
||||||
@ -2124,13 +2121,11 @@ class TestObjectController(unittest.TestCase):
|
|||||||
def fake_get_hashes(*args, **kwargs):
|
def fake_get_hashes(*args, **kwargs):
|
||||||
return 0, {1: 2}
|
return 0, {1: 2}
|
||||||
|
|
||||||
def my_tpool_execute(*args, **kwargs):
|
def my_tpool_execute(func, *args, **kwargs):
|
||||||
func = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
was_get_hashes = replicator.get_hashes
|
was_get_hashes = object_server.get_hashes
|
||||||
replicator.get_hashes = fake_get_hashes
|
object_server.get_hashes = fake_get_hashes
|
||||||
was_tpool_exe = tpool.execute
|
was_tpool_exe = tpool.execute
|
||||||
tpool.execute = my_tpool_execute
|
tpool.execute = my_tpool_execute
|
||||||
try:
|
try:
|
||||||
@ -2143,20 +2138,18 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertEquals(p_data, {1: 2})
|
self.assertEquals(p_data, {1: 2})
|
||||||
finally:
|
finally:
|
||||||
tpool.execute = was_tpool_exe
|
tpool.execute = was_tpool_exe
|
||||||
replicator.get_hashes = was_get_hashes
|
object_server.get_hashes = was_get_hashes
|
||||||
|
|
||||||
def test_REPLICATE_timeout(self):
|
def test_REPLICATE_timeout(self):
|
||||||
|
|
||||||
def fake_get_hashes(*args, **kwargs):
|
def fake_get_hashes(*args, **kwargs):
|
||||||
raise Timeout()
|
raise Timeout()
|
||||||
|
|
||||||
def my_tpool_execute(*args, **kwargs):
|
def my_tpool_execute(func, *args, **kwargs):
|
||||||
func = args[0]
|
|
||||||
args = args[1:]
|
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
was_get_hashes = replicator.get_hashes
|
was_get_hashes = object_server.get_hashes
|
||||||
replicator.get_hashes = fake_get_hashes
|
object_server.get_hashes = fake_get_hashes
|
||||||
was_tpool_exe = tpool.execute
|
was_tpool_exe = tpool.execute
|
||||||
tpool.execute = my_tpool_execute
|
tpool.execute = my_tpool_execute
|
||||||
try:
|
try:
|
||||||
@ -2166,7 +2159,7 @@ class TestObjectController(unittest.TestCase):
|
|||||||
self.assertRaises(Timeout, self.object_controller.REPLICATE, req)
|
self.assertRaises(Timeout, self.object_controller.REPLICATE, req)
|
||||||
finally:
|
finally:
|
||||||
tpool.execute = was_tpool_exe
|
tpool.execute = was_tpool_exe
|
||||||
replicator.get_hashes = was_get_hashes
|
object_server.get_hashes = was_get_hashes
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user