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-services4-logic-node";
import clock "github.com/pip-services4/pip-services4-go/pip-services4-logic-go/lock"
from pip_services4_logic.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();
lock := clock.NewMemoryLock()
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 timeout is 100 milliseconds. In the following example, we override this default by setting the timeout to 200 milliseconds:
let config = ConfigParams.fromTuples("retry_timeout", 200);
lock.configure(config);
config := conf.NewConfigParamsFromTuples("retry_timeout", 200)
lock.Configure(context.Background(), 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 traceId, 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 traceId equal to “123”, a key with the value “mykey”, and we set both timeouts to 1000 milliseconds:
await lock.acquireLock(ctx, "mykey", 1000, 1000);
lock.AcquireLock(context.Background(), "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 traceId and the key of a previously acquired lock as inputs. In the following example, we use the same traceId 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(ctx, "mykey");
lock.ReleaseLock(context.Background(), "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, Context, Descriptor,
IReferenceable, IReferences, References
} from "pip-services4-components-node";
import { MemoryLock, MemoryCache, ICache } from "pip-services4-logic-node";
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(ctx: Context, param1: string): Promise<void> {
// Lock
this._lock.acquireLock(ctx, "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(ctx, 'mykey', param1, 3600000);
// Release lock async
await this._lock.releaseLock(ctx, 'mykey');
}
public async obtainResult(ctx: Context): Promise<any> {
// Lock..
this._lock.acquireLock(ctx, "mykey", 1000, 1000);
// Do processing
// ...
let result = this._cache.retrieve(ctx, "mykey");
// Release lock async
await this._lock.releaseLock(ctx, "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(ctx, "param1");
let result = my_component.obtainResult(ctx);
console.log("The retrieved value is " + result);
import (
"context"
"fmt"
cconf "github.com/pip-services4/pip-services4-go/pip-services4-components-go/config"
crefer "github.com/pip-services4/pip-services4-go/pip-services4-components-go/refer"
ccache "github.com/pip-services4/pip-services4-go/pip-services4-logic-go/cache"
clock "github.com/pip-services4/pip-services4-go/pip-services4-logic-go/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, param1 string) {
// Lock
c.lock.AcquireLock(ctx, "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, "mykey", param1, 3600000)
// Release lock async
c.lock.ReleaseLock(ctx, "mykey")
}
func (c *MyComponent) ObtainResult(ctx context.Context) any {
// Lock..
c.lock.AcquireLock(ctx, "mykey", 1000, 1000)
// Do processing
// ...
result, _ := c.cache.Retrieve(ctx, "mykey")
// Release lock async
c.lock.ReleaseLock(ctx, "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(), "mykey", 1000, 1000)
lock.ReleaseLock(context.Background(), "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, "param1")
result := my_component.ObtainResult(ctx)
fmt.Println("The retrieved value is ", result)
}
from pip_services4_components.refer import Descriptor, References, IReferences, IReferenceable
from pip_services4_logic.cache import ICache, MemoryCache
from pip_services4_logic.lock.ILock import ILock
from pip_services4_logic.lock.MemoryLock import MemoryLock
from pip_services4_components.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-services4-logic-node";
import clock "github.com/pip-services4/pip-services4-go/pip-services4-logic-go/lock"
from pip_services4_logic.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();
lock := clock.NewNullLock()
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, Context, Descriptor,
IReferenceable, IReferences, References
} from "pip-services4-components-node";
import { NullLock, MemoryCache, MemoryLock, ICache } from "pip-services4-logic-node";
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(ctx: Context, param1: string): Promise<void> {
// Lock
this._lock.acquireLock(ctx, "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(ctx, 'mykey', param1, 3600000);
// Release lock async
await this._lock.releaseLock(ctx, 'mykey');
}
public async obtainResult(ctx: Context,): Promise<any> {
// Lock..
this._lock.acquireLock(ctx, "mykey", 1000, 1000);
// Do processing
// ...
let result = this._cache.retrieve(ctx, "mykey");
// Release lock async
await this._lock.releaseLock(ctx, "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(ctx, "param1");
let result = my_component.obtainResult(ctx);
console.log("The retrieved value is " + result);
from pip_services4_components.refer import Descriptor, References, IReferences, IReferenceable
from pip_services4_logic.cache import ICache, MemoryCache
from pip_services4_logic.lock.ILock import ILock
from pip_services4_logic.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.