Blog
A service's self.lock
method runs a block of code with a distributed lock held.
It is guaranteed that no other services throughout all servers in a cluster will concurrently run any code that is guarded by such a lock as long as the lock exists. This makes it possible to execute code that needs to be serialized and cannot be run in parallel.
Locks are named - by default a lock's name is the name of the service creating it but users are free to choose any names which allows one to form advanced interactions between locks and services.
Services can either wait for a lock to be available or give up immediately if another service is already holding one.
All locks eventually time out and disappear so they with time become available again.
Useful if a service cannot be invoked concurrently, for instance - if it accesses a resource that cannot handle parallel requests.
Using self.lock()
alone is equal to self.lock(self.name)
- i.e. the service's name becomes the lock's name in that case.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class UpdateCustomer(Service):
""" Updates customer data in CRM.
"""
def handle(self):
# A block of code that cannot run concurrently because,
# for instance, the remote end cannot handle it properly.
with self.lock():
# Supposing there is an outgoing REST connection to a remote CRM
conn = self.outgoing.plain_http['CRM'].conn
conn.send(self.cid, {'cust_id':123})
Locks can be given arbitrary names - that lets them be held by otherwise unrelated services that indeed don't even need to know of each other's existence.
The name may come from anywhere - below it is taken from a service's SimpleIO input with the net result being that for a given customer account only one service throughout the whole cluster can be executed.
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class UpdateAccount(Service):
""" Updates an account in CRM.
"""
class SimpleIO:
input_required = ('account_no', 'name')
def handle(self):
with self.lock(self.request.input.account_no):
# Update the account here ..
pass
# -*- coding: utf-8 -*-
# Zato
from zato.server.service import Service
class DeleteAccount(Service):
""" Deletes an account in CRM.
"""
class SimpleIO:
input_required = ('account_no',)
def handle(self):
with self.lock(self.request.input.account_no):
# Delete the account here ..
pass
By default, a lock expires after 20 seconds. That is, if a block of code does not complete within 20 seconds, the lock will expire anyway and it's possible another service will hold it even if the original one has not finished yet.
If longer expiration time is need, provide it on input, as below:
By default, if a lock cannot be obtained within 10 seconds an exception will be raised - you should catch the Python's generic Exception.
It is possible to provide information on how long to wait before giving up or not to wait at all if a lock cannot be created.
# Don't wait at all if we cannot have the lock, raise the exception straightaway
with self.lock(timeout=0):
...
Book a demo with an expert who will help you build meaningful systems that match your ambitions
"For me, Zato Source is the only technology partner to help with operational improvements."