Memory locks
Key takeaways
ILock | Interface that defines the main methods for locks. |
MemoryLock | Lock used to synchronize the execution of processes that use shared memory. |
NullLock | Dummy lock that performs no real actions. |
acquireLock() | Method used to acquire a lock by its key. |
releaseLock() | Method used to release a previously acquired lock by its key. |
Introduction
This tutorial will help you understand how to use the MemoryLock and NullLock components. First, we will explore the ILock interface, which must be implemented by all locks. Next, we will learn the basic functionality of the MemoryLock class, and we will construct an example that will demonstrate how to use this type of lock. After this, we will learn what the NullLock is, how it differentiates from the MemoryLock, and when it should be used. At the end, we will summarize all the concepts learned.
ILock
This interface defines the main methods that each lock must implement, specifically acquireLock(), tryAcquireLock() and releaseLock(). As their names suggest, the first two methods are used to acquire a lock, while the third is used to release an acquired lock. Both components, MemoryLock and NullLock, implement this interface. NullLock implements it directly, and MemoryLock - via its parent class Lock. The following diagram summarizes their relations:
MemoryLock
This component provides us with a lock that can be used to synchronize the execution of processes that use shared memory. In addition to its own methods, it inherits two important methods from the Lock class: acquireLock() and configure(). The following sections explain how to create, configure, acquire and release this type of lock.
Pre-requisites
In order to use this component, we need to import it first. The following example shows how to do this:
import { MemoryLock } from "pip-services3-components-nodex";
using PipServices3.Components.Lock;
import clock "github.com/pip-services3-gox/pip-services3-components-gox/lock"
import 'package:pip_services3_components/pip_services3_components.dart';
from pip_services3_components.lock.MemoryLock import MemoryLock
Lock creation
To create a lock, we just need to instantiate the MemoryLock class. The following line of code demonstrates this:
let lock = new MemoryLock();
var lockk = new MemoryLock();
lock := clock.NewMemoryLock()
var lock = MemoryLock();
lock = MemoryLock()
Lock configuration
Once we have an instance of a lock, we can configure the timeout (in milliseconds) to retry the lock acquisition via the configure() method. The default value is 100 (milliseconds), so, in the following example, we are overriding the default with a value of 200 (milliseconds):
let config = ConfigParams.fromTuples("retry_timeout", 200);
lock.configure(config);
var config = ConfigParams.FromTuples("retry_timeout", 200);
lockk.Configure(config);
config := cconf.NewConfigParamsFromTuples("retry_timeout", 200)
lock.Configure(context.Background(), config)
var lock = MemoryLock();
var config = ConfigParams.fromTuples(['retry_timeout', 200]);
lock.configure(config);
config = ConfigParams.from_tuples("retry_timeout", 200)
lock.configure(config)
Lock acquisition
After creation, a lock can be acquired through the acquireLock() method. This method accepts the correlationId, a key that identifies the lock, a lock timeout (milliseconds), and a lock acquisition timeout (milliseconds) as inputs. In the following example, we define the correlationId equal to “123”, a key with the value “mykey”, and we set both timeouts to 1000 milliseconds:
await lock.acquireLock("123", "mykey", 1000, 1000);
lockk.AcquireLock("123", "mykey", 1000, 1000);
lock.AcquireLock(context.Background(), "123", "mykey", 1000, 1000)
await lock.acquireLock('123', 'mykey', 1000, 1000);
lock.acquire_lock("123", "mykey", 1000, 1000, )
Lock release
Once done with, a lock can be released via the releaseLock() method. This method accepts the correlationId and the key of a previously acquired lock as inputs. In the following example, we use the same correlationId and key as in the previous example. In this manner, we can keep track of the process and identify the previously acquired lock.
await lock.releaseLock("123", "mykey");
lockk.ReleaseLock("123", "mykey");
lock.ReleaseLock(context.Background(), "123", "mykey")
await lock.releaseLock('123', 'mykey');
lock.release_lock("123", "mykey")
Example
Now that we have learned how to use the different methods available in this class, we can create an example that shows how they are used in practice.
In this example, we define a custom component with two methods. The first stores a value in memory, while the second retrieves the stored value and returns it. Both methods use a lock to manage their operations.
import { ConfigParams, Descriptor, IReferenceable, IReferences, References } from "pip-services3-commons-nodex";
import { ICache, MemoryCache, MemoryLock } from "pip-services3-components-nodex";
export class MyComponent implements IReferenceable {
private _cache: ICache;
private _lock: MemoryLock;
public setReferences(references: IReferences): void {
this._cache = references.getOneRequired(new Descriptor("*", "cache", "*", "*", "1.0"));
this._lock = references.getOneRequired(new Descriptor("*", "lock", "*", "*", "1.0"));
}
public async storeResult(correlationId: string, param1: string): Promise<void> {
// Lock
this._lock.acquireLock(correlationId, "mykey", 1000, 1000);
let config = ConfigParams.fromTuples("retry_timeout", 200);
this._lock.configure(config);
// Do processing
// ...
console.log("The stored value is " + param1);
// Store result to cache async
await this._cache.store(correlationId, 'mykey', param1, 3600000);
// Release lock async
await this._lock.releaseLock(correlationId, 'mykey');
}
public async obtainResult(correlationId: string): Promise<any> {
// Lock..
this._lock.acquireLock(correlationId, "mykey", 1000, 1000);
// Do processing
// ...
let result = this._cache.retrieve(correlationId, "mykey");
// Release lock async
await this._lock.releaseLock(correlationId, "mykey");
return result
}
}
// Use the component
let my_component = new MyComponent();
my_component.setReferences(References.fromTuples(
new Descriptor("pip-services", "cache", "memory", "default", "1.0"), new MemoryCache(),
new Descriptor("pip-services", "lock", "memory", "default", "1.0"), new MemoryLock(),
));
await my_component.storeResult(null, "param1");
let result = my_component.obtainResult(null);
console.log("The retrieved value is " + result);
using PipServices3.Commons.Config;
using PipServices3.Components.Lock;
using PipServices3.Components.Cache;
using PipServices3.Commons.Refer;
class MyComponent: IReferenceable
{
private ICache _cache;
private MemoryLock _lock;
public void SetReferences(IReferences references)
{
_cache = references.GetOneRequired<ICache>(new Descriptor("*", "cache", "*", "*", "1.0"));
_lock = references.GetOneRequired<MemoryLock>(new Descriptor("*", "lock", "*", "*", "1.0"));
}
public async Task StoreResultAsync(string correlationId, string param1)
{
// Lock
_lock.AcquireLock(correlationId, "mykey", 1000, 1000);
var config = ConfigParams.FromTuples("retry_timeout", 200);
this._lock.Configure(config);
// Do processing
// ...
Console.WriteLine("The stored value is " + param1);
// Store result to cache async
await _cache.StoreAsync(correlationId, "mykey", param1, 3600000);
// Release lock async
_lock.ReleaseLock(correlationId, "mykey");
}
public async Task<string> ObtainResultAsync(string correlationId)
{
// Lock..
this._lock.AcquireLock(correlationId, "mykey", 1000, 1000);
// Do processing
// ...
var result = await this._cache.RetrieveAsync<string>(correlationId, "mykey");
// Release lock async
this._lock.ReleaseLock(correlationId, "mykey");
return result;
}
}
// Use the component
var my_component = new MyComponent();
my_component.SetReferences(References.FromTuples(
new Descriptor("pip-services", "cache", "memory", "default", "1.0"), new MemoryCache(),
new Descriptor("pip-services", "lock", "memory", "default", "1.0"), new MemoryLock()
));
await my_component.StoreResultAsync(null, "param1");
var result = my_component.ObtainResultAsync(null);
Console.WriteLine("The retrieved value is " + result);
import (
"context"
"fmt"
cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
crefer "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
ccache "github.com/pip-services3-gox/pip-services3-components-gox/cache"
clock "github.com/pip-services3-gox/pip-services3-components-gox/lock"
)
type MyComponent struct {
cache ccache.ICache[any]
lock clock.MemoryLock
}
func (c *MyComponent) SetReferences(ctx context.Context, references crefer.IReferences) {
res, descrErr := references.GetOneRequired(crefer.NewDescriptor("*", "cache", "*", "*", "1.0"))
if descrErr != nil {
panic(descrErr)
}
c.cache = res.(ccache.ICache[any])
res, descrErr = references.GetOneRequired(crefer.NewDescriptor("*", "lock", "*", "*", "1.0"))
if descrErr != nil {
panic(descrErr)
}
c.lock = res.(clock.MemoryLock)
}
func (c *MyComponent) StoreResult(ctx context.Context, correlationId string, param1 string) {
// Lock
c.lock.AcquireLock(ctx, correlationId, "mykey", 1000, 1000)
config := cconf.NewConfigParamsFromTuples("retry_timeout", 200)
c.lock.Configure(ctx, config)
// Do processing
// ...
fmt.Println("The stored value is " + param1)
// Store result to cache async
c.cache.Store(ctx, correlationId, "mykey", param1, 3600000)
// Release lock async
c.lock.ReleaseLock(ctx, correlationId, "mykey")
}
func (c *MyComponent) ObtainResult(ctx context.Context, correlationId string) any {
// Lock..
c.lock.AcquireLock(ctx, correlationId, "mykey", 1000, 1000)
// Do processing
// ...
result, err := c.cache.Retrieve(ctx, correlationId, "mykey")
// Release lock async
c.lock.ReleaseLock(ctx, correlationId, "mykey")
return result
}
func main() {
ctx := context.Background()
lock := clock.NewMemoryLock()
config := cconf.NewConfigParamsFromTuples("retry_timeout", 200)
lock.Configure(context.Background(), config)
lock.AcquireLock(context.Background(), "123", "mykey", 1000, 1000)
lock.ReleaseLock(context.Background(), "123", "mykey")
// Use the component
my_component := &MyComponent{}
my_component.SetReferences(ctx, crefer.NewReferencesFromTuples(ctx,
crefer.NewDescriptor("pip-services", "cache", "memory", "default", "1.0"), ccache.NewMemoryCache[any](),
crefer.NewDescriptor("pip-services", "lock", "memory", "default", "1.0"), clock.NewMemoryLock(),
))
my_component.StoreResult(ctx, "123", "param1")
result := my_component.ObtainResult(ctx, "123")
fmt.Println("The retrieved value is " + result)
}
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_components/pip_services3_components.dart';
class MyComponent implements IReferenceable {
late ICache _cache;
late MemoryLock _lock;
@override
void setReferences(IReferences references) {
_cache =
references.getOneRequired(Descriptor('*', 'cache', '*', '*', '1.0'));
_lock = references.getOneRequired(Descriptor('*', 'lock', '*', '*', '1.0'));
}
Future<void> storeResult(String? correlationId, String param1) async {
// Lock
await _lock.acquireLock(correlationId, 'mykey', 1000, 1000);
var config = ConfigParams.fromTuples(['retry_timeout', 200]);
_lock.configure(config);
// Do processing
// ...
print('The stored value is ' + param1);
// Store result to cache async
await _cache.store(correlationId, 'mykey', param1, 3600000);
// Release lock async
await _lock.releaseLock(correlationId, 'mykey');
}
Future<String> obtainResult(String? correlationId) async {
// Lock..
await _lock.acquireLock(correlationId, 'mykey', 1000, 1000);
// Do processing
// ...
var result = await _cache.retrieve(correlationId, 'mykey') as String;
// Release lock async
await _lock.releaseLock(correlationId, 'mykey');
return result;
}
}
// Use the component
var my_component = MyComponent();
my_component.setReferences(References.fromTuples([
Descriptor('pip-services', 'cache', 'memory', 'default', '1.0'),
MemoryCache(),
Descriptor('pip-services', 'lock', 'memory', 'default', '1.0'),
MemoryLock()
]));
await my_component.storeResult(null, 'param1');
var result = await my_component.obtainResult(null);
print('The retrieved value is ' + result);
from pip_services3_commons.refer import Descriptor, References, IReferences, IReferenceable
from pip_services3_components.cache import ICache, MemoryCache
from pip_services3_components.lock.ILock import ILock
from pip_services3_components.lock.MemoryLock import MemoryLock
from pip_services3_commons.config import ConfigParams
class MyComponent(IReferenceable):
__cache: ICache
__lock: ILock
def set_references(self, references: IReferences):
self.__cache = references.get_one_required(Descriptor("*", "cache", "*", "*", "1.0"))
self.__lock = references.get_one_required(Descriptor("*", "lock", "*", "*", "1.0"))
def store_result(self, correlation_id, param1):
print("The stored value is " + param1)
# Lock
self.__lock.acquire_lock(correlation_id, "mykey", 1000, 1000, )
config = ConfigParams.from_tuples("retry_timeout", 200)
self.__lock.configure(config)
# Do processing
# ...
# Store result to cache async
self.__cache.store(correlation_id, 'mykey', param1, 3600000)
# Release lock async
self.__lock.release_lock(correlation_id, 'mykey')
def obtain_result(self, correlation_id):
# Lock..
self.__lock.acquire_lock(correlation_id, "mykey", 1000, 1000, )
# Do processing
# ...
result = self.__cache.retrieve(correlation_id, "mykey")
# Release lock async
self.__lock.release_lock(correlation_id, "mykey")
return result
# Use the component
my_component = MyComponent()
my_component.set_references(References.from_tuples(
Descriptor("pip-services", "cache", "memory", "default", "1.0"), MemoryCache(),
Descriptor("pip-services", "lock", "memory", "default", "1.0"), MemoryLock(),
))
my_component.store_result(None, "param1")
result = my_component.obtain_result(None)
print("The retrieved value is " + result)
And, after running the above code, we obtain the following results:
NullLock
This component represents a dummy lock that produces no real results. As such, it can be used for testing purposes or in situations where a lock is required, but needs to be disabled. Another point worth mentioning is that this class doesn’t contain the configure() method. .
Pre-requisites
In order to use this component, we need to import it first. The following example shows how to do this:
import { NullLock } from "pip-services3-components-nodex";
using PipServices3.Components.Lock;
import clock "github.com/pip-services3-gox/pip-services3-components-gox/lock"
import 'package:pip_services3_components/pip_services3_components.dart';
from pip_services3_components.lock.NullLock import NullLock
Lock creation
To create a NullLock, we need to instantiate it. The following line of code demonstrates this:
let lock = new NullLock();
var lockk = new NullLock();
lock := clock.NewNullLock()
var lock = NullLock();
lock = NullLock()
Lock acquisition and release
The Null lock class does possess the acquireLock() and releaseLock() methods, which can be called in the same manner as they were called for the MemoryLock class. The only difference is that they don’t actually acquire or release any locks, but only simulate these operations.
Example
The following example replaces the MemoryLock used in the previous example with a NullLock. Thus, the locking is only simulated and will not actually prevent simultaneous reading of and/or writing to shared memory.
import { ConfigParams, Descriptor, IReferenceable, IReferences, References } from "pip-services3-commons-nodex";
import { ICache, MemoryCache, MemoryLock } from "pip-services3-components-nodex";
export class MyComponent implements IReferenceable {
private _cache: ICache;
private _lock: MemoryLock;
public setReferences(references: IReferences): void {
this._cache = references.getOneRequired(new Descriptor("*", "cache", "*", "*", "1.0"));
this._lock = references.getOneRequired(new Descriptor("*", "lock", "*", "*", "1.0"));
}
public async storeResult(correlationId: string, param1: string): Promise<void> {
// Lock
this._lock.acquireLock(correlationId, "mykey", 1000, 1000);
let config = ConfigParams.fromTuples("retry_timeout", 200);
this._lock.configure(config);
// Do processing
// ...
console.log("The stored value is " + param1);
// Store result to cache async
await this._cache.store(correlationId, 'mykey', param1, 3600000);
// Release lock async
await this._lock.releaseLock(correlationId, 'mykey');
}
public async obtainResult(correlationId: string): Promise<any> {
// Lock..
this._lock.acquireLock(correlationId, "mykey", 1000, 1000);
// Do processing
// ...
let result = this._cache.retrieve(correlationId, "mykey");
// Release lock async
await this._lock.releaseLock(correlationId, "mykey");
return result
}
}
// Use the component
let my_component = new MyComponent();
my_component.setReferences(References.fromTuples(
new Descriptor("pip-services", "cache", "memory", "default", "1.0"), new MemoryCache(),
new Descriptor("pip-services", "lock", "null", "default", "1.0"), new NullLock(),
));
await my_component.storeResult(null, "param1");
let result = my_component.obtainResult(null);
console.log("The retrieved value is " + result);
using PipServices3.Commons.Config;
using PipServices3.Components.Lock;
using PipServices3.Components.Cache;
using PipServices3.Commons.Refer;
class MyComponent: IReferenceable
{
private ICache _cache;
private MemoryLock _lock;
public void SetReferences(IReferences references)
{
_cache = references.GetOneRequired<ICache>(new Descriptor("*", "cache", "*", "*", "1.0"));
_lock = references.GetOneRequired<MemoryLock>(new Descriptor("*", "lock", "*", "*", "1.0"));
}
public async Task StoreResultAsync(string correlationId, string param1)
{
// Lock
_lock.AcquireLock(correlationId, "mykey", 1000, 1000);
var config = ConfigParams.FromTuples("retry_timeout", 200);
this._lock.Configure(config);
// Do processing
// ...
Console.WriteLine("The stored value is " + param1);
// Store result to cache async
await _cache.StoreAsync(correlationId, "mykey", param1, 3600000);
// Release lock async
_lock.ReleaseLock(correlationId, "mykey");
}
public async Task<string> ObtainResultAsync(string correlationId)
{
// Lock..
this._lock.AcquireLock(correlationId, "mykey", 1000, 1000);
// Do processing
// ...
var result = await this._cache.RetrieveAsync<string>(correlationId, "mykey");
// Release lock async
this._lock.ReleaseLock(correlationId, "mykey");
return result;
}
}
// Use the component
var my_component = new MyComponent();
my_component.SetReferences(References.FromTuples(
new Descriptor("pip-services", "cache", "memory", "default", "1.0"), new MemoryCache(),
new Descriptor("pip-services", "lock", "null", "default", "1.0"), new NullLock()
));
await my_component.StoreResultAsync(null, "param1");
var result = my_component.ObtainResultAsync(null);
Console.WriteLine("The retrieved value is " + result);
import (
"context"
"fmt"
cconf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
crefer "github.com/pip-services3-gox/pip-services3-commons-gox/refer"
ccache "github.com/pip-services3-gox/pip-services3-components-gox/cache"
clock "github.com/pip-services3-gox/pip-services3-components-gox/lock"
)
type MyComponent struct {
cache ccache.ICache[any]
lock clock.MemoryLock
}
func (c *MyComponent) SetReferences(ctx context.Context, references crefer.IReferences) {
res, descrErr := references.GetOneRequired(crefer.NewDescriptor("*", "cache", "*", "*", "1.0"))
if descrErr != nil {
panic(descrErr)
}
c.cache = res.(ccache.ICache[any])
res, descrErr = references.GetOneRequired(crefer.NewDescriptor("*", "lock", "*", "*", "1.0"))
if descrErr != nil {
panic(descrErr)
}
c.lock = res.(clock.MemoryLock)
}
func (c *MyComponent) StoreResult(ctx context.Context, correlationId string, param1 string) {
// Lock
c.lock.AcquireLock(ctx, correlationId, "mykey", 1000, 1000)
config := cconf.NewConfigParamsFromTuples("retry_timeout", 200)
c.lock.Configure(ctx, config)
// Do processing
// ...
fmt.Println("The stored value is " + param1)
// Store result to cache async
c.cache.Store(ctx, correlationId, "mykey", param1, 3600000)
// Release lock async
c.lock.ReleaseLock(ctx, correlationId, "mykey")
}
func (c *MyComponent) ObtainResult(ctx context.Context, correlationId string) any {
// Lock..
c.lock.AcquireLock(ctx, correlationId, "mykey", 1000, 1000)
// Do processing
// ...
result, err := c.cache.Retrieve(ctx, correlationId, "mykey")
// Release lock async
c.lock.ReleaseLock(ctx, correlationId, "mykey")
return result
}
func main() {
ctx := context.Background()
lock := clock.NewMemoryLock()
config := cconf.NewConfigParamsFromTuples("retry_timeout", 200)
lock.Configure(context.Background(), config)
lock.AcquireLock(context.Background(), "123", "mykey", 1000, 1000)
lock.ReleaseLock(context.Background(), "123", "mykey")
// Use the component
my_component := &MyComponent{}
my_component.SetReferences(ctx, crefer.NewReferencesFromTuples(ctx,
crefer.NewDescriptor("pip-services", "cache", "memory", "default", "1.0"), ccache.NewMemoryCache[any](),
crefer.NewDescriptor("pip-services", "lock", "memory", "default", "1.0"), clock.NewNullLock(),
))
my_component.StoreResult(ctx, "123", "param1")
result := my_component.ObtainResult(ctx, "123")
fmt.Println("The retrieved value is " + result)
}
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_components/pip_services3_components.dart';
class MyComponent implements IReferenceable {
late ICache _cache;
late MemoryLock _lock;
@override
void setReferences(IReferences references) {
_cache =
references.getOneRequired(Descriptor('*', 'cache', '*', '*', '1.0'));
_lock = references.getOneRequired(Descriptor('*', 'lock', '*', '*', '1.0'));
}
Future<void> storeResult(String? correlationId, String param1) async {
// Lock
await _lock.acquireLock(correlationId, 'mykey', 1000, 1000);
var config = ConfigParams.fromTuples(['retry_timeout', 200]);
_lock.configure(config);
// Do processing
// ...
print('The stored value is ' + param1);
// Store result to cache async
await _cache.store(correlationId, 'mykey', param1, 3600000);
// Release lock async
await _lock.releaseLock(correlationId, 'mykey');
}
Future<String> obtainResult(String? correlationId) async {
// Lock..
await _lock.acquireLock(correlationId, 'mykey', 1000, 1000);
// Do processing
// ...
var result = await _cache.retrieve(correlationId, 'mykey') as String;
// Release lock async
await _lock.releaseLock(correlationId, 'mykey');
return result;
}
}
// Use the component
var my_component = MyComponent();
my_component.setReferences(References.fromTuples([
Descriptor('pip-services', 'cache', 'memory', 'default', '1.0'),
MemoryCache(),
Descriptor('pip-services', 'lock', 'null', 'default', '1.0'),
NullLock()
]));
await my_component.storeResult(null, 'param1');
var result = await my_component.obtainResult(null);
print('The retrieved value is ' + result);
from pip_services3_commons.refer import Descriptor, References, IReferences, IReferenceable
from pip_services3_components.cache import ICache, MemoryCache
from pip_services3_components.lock.ILock import ILock
from pip_services3_components.lock.NullLock import NullLock
class MyComponent(IReferenceable):
__cache: ICache
__lock: ILock
def set_references(self, references: IReferences):
self.__cache = references.get_one_required(Descriptor("*", "cache", "*", "*", "1.0"))
self.__lock = references.get_one_required(Descriptor("*", "lock", "*", "*", "1.0"))
def store_result(self, correlation_id, param1):
# Lock
self.__lock.acquire_lock(correlation_id, "mykey", 1000, 1000, )
# Do processing
# ...
print("The stored value is " + param1)
# Store result to cache async
self.__cache.store(correlation_id, 'mykey', param1, 3600000)
# Release lock async
self.__lock.release_lock(correlation_id, 'mykey')
def obtain_result(self, correlation_id):
# Lock..
self.__lock.acquire_lock(correlation_id, "mykey", 1000, 1000, )
# Do processing
# ...
result = self.__cache.retrieve(correlation_id, "mykey")
# Release lock async
self.__lock.release_lock(correlation_id, "mykey")
return result
# Use the component
my_component = MyComponent()
my_component.set_references(References.from_tuples(
Descriptor("pip-services", "cache", "memory", "default", "1.0"), MemoryCache(),
Descriptor("pip-services", "lock", "null", "default", "1.0"), NullLock(),
))
my_component.store_result(None, "param1")
result = my_component.obtain_result(None)
print("The retrieved value is " + result)
Which, after running, produces the following outcome:
Wrapping up
In this tutorial, we have learned how to use the MemoryLock and NullLock components. First, we examined the ILock interface, which must be implemented by all locks. Next, we learned that the MemoryLock class allows us to create a lock that can be used to synchronize the execution of processes that use shared memory. Then, we saw how to create, acquire and release this type of lock and an example of how to apply these mechanisms inside a component. Finally, we learned that the NullLock is a dummy component that only simulates the behavior of a real lock.