Add a generic coordination lock mechanism
For various synchronized scenarios, this decorator allows flexible lock name with parameters and names of underlying functions. For instance: @synchronized('{f_name}-{resource.id}-{snap[name]}') def foo(self, resource, snap): Change-Id: I4bf75be2902cd598a5a5a2c5887d4b4262f3e042 Related-Bug: #1824911
This commit is contained in:
parent
6002df86fb
commit
975143fd08
96
neutron/common/coordination.py
Normal file
96
neutron/common/coordination.py
Normal file
@ -0,0 +1,96 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Coordination and locking utilities."""
|
||||
|
||||
import inspect
|
||||
|
||||
import decorator
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_log import log
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
def synchronized(lock_name):
|
||||
"""Synchronization decorator.
|
||||
|
||||
:param str lock_name: Lock name.
|
||||
|
||||
Decorating a method like so::
|
||||
|
||||
@synchronized('mylock')
|
||||
def foo(self, *args):
|
||||
...
|
||||
|
||||
ensures that only one process will execute the foo method at a time.
|
||||
|
||||
Different methods can share the same lock::
|
||||
|
||||
@synchronized('mylock')
|
||||
def foo(self, *args):
|
||||
...
|
||||
|
||||
@synchronized('mylock')
|
||||
def bar(self, *args):
|
||||
...
|
||||
|
||||
This way only one of either foo or bar can be executing at a time.
|
||||
|
||||
Lock name can be formatted using Python format string syntax::
|
||||
|
||||
@synchronized('{f_name}-{resource.id}-{snap[name]}')
|
||||
def foo(self, resource, snap):
|
||||
...
|
||||
|
||||
Available field names are: decorated function parameters and
|
||||
`f_name` as a decorated function name.
|
||||
"""
|
||||
|
||||
@decorator.decorator
|
||||
def _synchronized(f, *a, **k):
|
||||
if six.PY2:
|
||||
# pylint: disable=deprecated-method
|
||||
call_args = inspect.getcallargs(f, *a, **k)
|
||||
else:
|
||||
sig = inspect.signature(f).bind(*a, **k)
|
||||
sig.apply_defaults()
|
||||
call_args = sig.arguments
|
||||
call_args['f_name'] = f.__name__
|
||||
lock_format_name = lock_name.format(**call_args)
|
||||
t1 = timeutils.now()
|
||||
t2 = None
|
||||
try:
|
||||
with lockutils.lock(lock_format_name):
|
||||
t2 = timeutils.now()
|
||||
LOG.debug('Lock "%(name)s" acquired by "%(function)s" :: '
|
||||
'waited %(wait_secs)0.3fs',
|
||||
{'name': lock_format_name,
|
||||
'function': f.__name__,
|
||||
'wait_secs': (t2 - t1)})
|
||||
return f(*a, **k)
|
||||
finally:
|
||||
t3 = timeutils.now()
|
||||
if t2 is None:
|
||||
held_secs = "N/A"
|
||||
else:
|
||||
held_secs = "%0.3fs" % (t3 - t2)
|
||||
LOG.debug('Lock "%(name)s" released by "%(function)s" :: held '
|
||||
'%(held_secs)s',
|
||||
{'name': lock_format_name,
|
||||
'function': f.__name__,
|
||||
'held_secs': held_secs})
|
||||
|
||||
return _synchronized
|
33
neutron/tests/unit/common/test_coordination.py
Normal file
33
neutron/tests/unit/common/test_coordination.py
Normal file
@ -0,0 +1,33 @@
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_concurrency import lockutils
|
||||
|
||||
from neutron.common import coordination
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
@mock.patch.object(lockutils, 'lock')
|
||||
class CoordinationTestCase(base.BaseTestCase):
|
||||
def test_synchronized(self, get_lock):
|
||||
@coordination.synchronized('lock-{f_name}-{foo.val}-{bar[val]}')
|
||||
def func(foo, bar):
|
||||
pass
|
||||
|
||||
foo = mock.Mock()
|
||||
foo.val = 7
|
||||
bar = mock.MagicMock()
|
||||
bar.__getitem__.return_value = 8
|
||||
func(foo, bar)
|
||||
get_lock.assert_called_with('lock-func-7-8')
|
10
releasenotes/notes/coordination-df3c0bf55a0c4863.yaml
Normal file
10
releasenotes/notes/coordination-df3c0bf55a0c4863.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
other:
|
||||
- |
|
||||
Add a generic coordination lock mechanism for various
|
||||
scenarios. This decorator allows flexible lock name
|
||||
with parameters and names of underlying functions. And
|
||||
in order to achive backward compatibility with python2.7
|
||||
several functions was copied from the old version of
|
||||
python inspect. Once python2.7 is retired, we can drop
|
||||
such duplication.
|
@ -7,6 +7,7 @@ Paste>=2.0.2 # MIT
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
Routes>=2.3.1 # MIT
|
||||
debtcollector>=1.2.0 # Apache-2.0
|
||||
decorator>=3.4.0 # BSD
|
||||
eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
|
||||
pecan>=1.3.2 # BSD
|
||||
httplib2>=0.9.1 # MIT
|
||||
|
Loading…
Reference in New Issue
Block a user