Source code for etcd3gw.lock
# 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 types
from typing import TYPE_CHECKING
import uuid
from etcd3gw import exceptions
from etcd3gw.utils import _encode
from etcd3gw.utils import DEFAULT_TIMEOUT
from etcd3gw.utils import LOCK_PREFIX
if TYPE_CHECKING:
from etcd3gw import client as _client_module
from etcd3gw import lease as _lease_module
[docs]
class Lock:
def __init__(
self,
name: str,
ttl: int = DEFAULT_TIMEOUT,
*,
client: '_client_module.Etcd3Client',
) -> None:
"""Create a lock using the given name with specified timeout
:param name:
:param ttl:
:param client:
"""
self.name = name
self.ttl = ttl
self.client: _client_module.Etcd3Client = client
self.key = LOCK_PREFIX + self.name
self.lease: _lease_module.Lease | None = None
self._uuid = str(uuid.uuid1())
@property
def uuid(self) -> str:
"""The unique id of the lock"""
return self._uuid
[docs]
def acquire(self) -> bool:
"""Acquire the lock."""
self.lease = self.client.lease(self.ttl)
base64_key = _encode(self.key)
base64_value = _encode(self._uuid)
txn = {
'compare': [
{
'key': base64_key,
'result': 'EQUAL',
'target': 'CREATE',
'create_revision': 0,
}
],
'success': [
{
'request_put': {
'key': base64_key,
'value': base64_value,
'lease': self.lease.id,
}
}
],
'failure': [{'request_range': {'key': base64_key}}],
}
result = self.client.transaction(txn)
if 'succeeded' in result:
return result['succeeded']
return False
[docs]
def release(self) -> bool:
"""Release the lock"""
base64_key = _encode(self.key)
base64_value = _encode(self._uuid)
txn = {
'compare': [
{
'key': base64_key,
'result': 'EQUAL',
'target': 'VALUE',
'value': base64_value,
}
],
'success': [{'request_delete_range': {'key': base64_key}}],
}
result = self.client.transaction(txn)
if 'succeeded' in result:
return result['succeeded']
return False
[docs]
def refresh(self) -> int:
"""Refresh the lease on the lock
:return:
"""
if self.lease is None:
raise exceptions.Etcd3Exception('lease must be acquired first')
return self.lease.refresh()
[docs]
def is_acquired(self) -> bool:
"""Check if the lock is acquired"""
values = self.client.get(self.key)
return self._uuid.encode("latin-1") in values
def __enter__(self) -> 'Lock':
"""Use the lock as a contextmanager"""
self.acquire()
return self
def __exit__(
self,
exception_type: type[BaseException] | None,
exception_value: BaseException | None,
traceback: types.TracebackType | None,
) -> None:
self.release()