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
|
@ -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
|
|
@ -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')
|
|
@ -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
|
PasteDeploy>=1.5.0 # MIT
|
||||||
Routes>=2.3.1 # MIT
|
Routes>=2.3.1 # MIT
|
||||||
debtcollector>=1.2.0 # Apache-2.0
|
debtcollector>=1.2.0 # Apache-2.0
|
||||||
|
decorator>=3.4.0 # BSD
|
||||||
eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
|
eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
|
||||||
pecan>=1.3.2 # BSD
|
pecan>=1.3.2 # BSD
|
||||||
httplib2>=0.9.1 # MIT
|
httplib2>=0.9.1 # MIT
|
||||||
|
|
Loading…
Reference in New Issue