Memory locks

How to use the MemoryLock and NullLock components.

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:

figure 2

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";
Not available
import clock "github.com/pip-services4/pip-services4-go/pip-services4-logic-go/lock"
Not available
from pip_services4_logic.lock.MemoryLock import MemoryLock
Not available

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();
Not available
lock := clock.NewMemoryLock()
Not available
lock = MemoryLock()
Not available

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);
Not available
config := conf.NewConfigParamsFromTuples("retry_timeout", 200)
lock.Configure(context.Background(), config)
Not available
config = ConfigParams.from_tuples("retry_timeout", 200)
lock.configure(config)

Not available

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);

Not available
lock.AcquireLock(context.Background(), "mykey", 1000, 1000)
Not available
lock.acquire_lock("123", "mykey", 1000, 1000, )
Not available

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");

Not available
lock.ReleaseLock(context.Background(), "mykey")
Not available
lock.release_lock("123", "mykey")
Not available

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);
Not available
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)
}
Not available
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)

Not available

And, after running the above code, we obtain the following results:

figure 1

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";
Not available
import clock "github.com/pip-services4/pip-services4-go/pip-services4-logic-go/lock"
Not available
from pip_services4_logic.lock.NullLock import NullLock
Not available

Lock creation

To create a NullLock, we need to instantiate it. The following line of code demonstrates this:

let lock = new NullLock();

Not available
lock := clock.NewNullLock()
Not available
lock = NullLock()
Not available

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);
Not available
Not available
Not available
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)
Not available

Which, after running, produces the following outcome:

figure 1

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.