Skip to content

Commit ea3945f

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add wrapper for oslo.concurrency lockutils.ReaderWriterLock()"
2 parents 8c714c7 + 887c445 commit ea3945f

File tree

5 files changed

+86
-20
lines changed

5 files changed

+86
-20
lines changed

nova/hacking/checks.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@
136136
# Regex for catching aliasing mock.Mock class in test
137137
mock_class_as_new_value_in_patching_re = re.compile(
138138
r"mock\.patch(\.object)?.* new=mock\.(Magic|NonCallable)?Mock[^(]")
139+
# Regex for direct use of oslo.concurrency lockutils.ReaderWriterLock
140+
rwlock_re = re.compile(
141+
r"(?P<module_part>(oslo_concurrency\.)?(lockutils|fasteners))"
142+
r"\.ReaderWriterLock\(.*\)")
139143

140144

141145
class BaseASTChecker(ast.NodeVisitor):
@@ -1001,3 +1005,28 @@ def do_not_use_mock_class_as_new_mock_value(logical_line, filename):
10011005
"leak out from the test and can cause interference. "
10021006
"Use new=mock.Mock() or new_callable=mock.Mock instead."
10031007
)
1008+
1009+
1010+
@core.flake8ext
1011+
def check_lockutils_rwlocks(logical_line):
1012+
"""Check for direct use of oslo.concurrency lockutils.ReaderWriterLock()
1013+
1014+
oslo.concurrency lockutils uses fasteners.ReaderWriterLock to provide
1015+
read/write locks and fasteners calls threading.current_thread() to track
1016+
and identify lock holders and waiters. The eventlet implementation of
1017+
current_thread() only supports greenlets of type GreenThread, else it falls
1018+
back on the native threading.current_thread() method.
1019+
1020+
See https://github.com/eventlet/eventlet/issues/731 for details.
1021+
1022+
N369
1023+
"""
1024+
msg = ("N369: %(module)s.ReaderWriterLock() does not "
1025+
"function correctly with eventlet patched code. "
1026+
"Use nova.utils.ReaderWriterLock() instead.")
1027+
match = re.match(rwlock_re, logical_line)
1028+
if match:
1029+
yield (
1030+
0,
1031+
msg % {'module': match.group('module_part')}
1032+
)

nova/tests/fixtures/nova.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
"""Fixtures for Nova tests."""
1818

1919
import collections
20-
import contextlib
2120
from contextlib import contextmanager
2221
import functools
2322
import logging as std_logging
@@ -29,7 +28,6 @@
2928
import futurist
3029
import mock
3130
from openstack import service_description
32-
from oslo_concurrency import lockutils
3331
from oslo_config import cfg
3432
from oslo_db import exception as db_exc
3533
from oslo_db.sqlalchemy import enginefacade
@@ -407,24 +405,7 @@ def __init__(self):
407405
# to point to a cell, we need to take an exclusive lock to
408406
# prevent any other calls to get_context_manager() until we
409407
# reset to the default.
410-
# NOTE(melwitt): As of fasteners >= 0.15, the workaround code to use
411-
# eventlet.getcurrent if eventlet patching is detected has been removed
412-
# and threading.current_thread is being used instead. Although we are
413-
# running in a greenlet in our test environment, we are not running in
414-
# a greenlet of type GreenThread. A GreenThread is created by calling
415-
# eventlet.spawn and spawn is not used to run our tests. At the time of
416-
# this writing, the eventlet patched threading.current_thread method
417-
# falls back to the original unpatched current_thread method if it is
418-
# not called from a GreenThead [1] and that breaks our tests involving
419-
# this fixture.
420-
# We can work around this by patching threading.current_thread with
421-
# eventlet.getcurrent during creation of the lock object.
422-
# [1] https://github.com/eventlet/eventlet/blob/v0.32.0/eventlet/green/threading.py#L128 # noqa
423-
eventlet_patched = eventlet.patcher.is_monkey_patched('thread')
424-
with (contextlib.ExitStack() if not eventlet_patched else
425-
fixtures.MonkeyPatch('threading.current_thread',
426-
eventlet.getcurrent)):
427-
self._cell_lock = lockutils.ReaderWriterLock()
408+
self._cell_lock = utils.ReaderWriterLock()
428409

429410
def _cache_schema(self, connection_str):
430411
# NOTE(melwitt): See the regular Database fixture for why

nova/tests/unit/test_hacking.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,3 +1000,23 @@ def a():
10001000
self._assert_has_no_errors(
10011001
code, checks.do_not_use_mock_class_as_new_mock_value,
10021002
filename="nova/tests/unit/test_context.py")
1003+
1004+
def test_check_lockutils_rwlocks(self):
1005+
code = """
1006+
lockutils.ReaderWriterLock()
1007+
lockutils.ReaderWriterLock(condition_cls=MyClass)
1008+
oslo_concurrency.lockutils.ReaderWriterLock()
1009+
fasteners.ReaderWriterLock()
1010+
fasteners.ReaderWriterLock(condition_cls=MyClass)
1011+
"""
1012+
errors = [(x + 1, 0, 'N369') for x in range(5)]
1013+
self._assert_has_errors(
1014+
code, checks.check_lockutils_rwlocks, expected_errors=errors)
1015+
1016+
code = """
1017+
nova.utils.ReaderWriterLock()
1018+
utils.ReaderWriterLock()
1019+
utils.ReaderWriterLock(condition_cls=MyClass)
1020+
nova_utils.ReaderWriterLock()
1021+
"""
1022+
self._assert_has_no_errors(code, checks.check_lockutils_rwlocks)

nova/utils.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import tempfile
3030

3131
import eventlet
32+
import fixtures
3233
from keystoneauth1 import loading as ks_loading
3334
import netaddr
3435
from openstack import connection
@@ -1143,3 +1144,37 @@ def reset(wrapper, *args, **kwargs):
11431144
wrapper.reset = functools.partial(reset, wrapper)
11441145
return wrapper
11451146
return outer_wrapper
1147+
1148+
1149+
class ReaderWriterLock(lockutils.ReaderWriterLock):
1150+
"""Wrap oslo.concurrency lockutils.ReaderWriterLock to support eventlet.
1151+
1152+
As of fasteners >= 0.15, the workaround code to use eventlet.getcurrent()
1153+
if eventlet patching is detected has been removed and
1154+
threading.current_thread is being used instead. Although we are running in
1155+
a greenlet in our test environment, we are not running in a greenlet of
1156+
type GreenThread. A GreenThread is created by calling eventlet.spawn() and
1157+
spawn() is not used to run our tests. At the time of this writing, the
1158+
eventlet patched threading.current_thread() method falls back to the
1159+
original unpatched current_thread() method if it is not called from a
1160+
GreenThead [1] and that breaks our tests involving this fixture.
1161+
1162+
We can work around this by patching threading.current_thread() with
1163+
eventlet.getcurrent() during creation of the lock object, if we detect we
1164+
are eventlet patched. If we are not eventlet patched, we use a no-op
1165+
context manager.
1166+
1167+
Note: this wrapper should be used for any ReaderWriterLock because any lock
1168+
may possibly be running inside a plain greenlet created by spawn_n().
1169+
1170+
See https://github.com/eventlet/eventlet/issues/731 for details.
1171+
1172+
[1] https://github.com/eventlet/eventlet/blob/v0.32.0/eventlet/green/threading.py#L128 # noqa
1173+
"""
1174+
1175+
def __init__(self, *a, **kw):
1176+
eventlet_patched = eventlet.patcher.is_monkey_patched('thread')
1177+
mpatch = fixtures.MonkeyPatch(
1178+
'threading.current_thread', eventlet.getcurrent)
1179+
with mpatch if eventlet_patched else contextlib.ExitStack():
1180+
return super().__init__(*a, **kw)

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ extension =
346346
N366 = checks:check_assert_has_calls
347347
N367 = checks:do_not_alias_mock_class
348348
N368 = checks:do_not_use_mock_class_as_new_mock_value
349+
N369 = checks:check_lockutils_rwlocks
349350
paths =
350351
./nova/hacking
351352

0 commit comments

Comments
 (0)