Class: LockPowers
Powers to provide synchronization primitives for Andi skills across concurrent async processes, replicas, and beyond. These primitives are distributed and can be shared across skill versions or across all skills in an organization.
Mutexes help exclusive access to a shared resource.
Semaphores help with allowing shared access to a shared resource, up to a defined limit.
Methods
createMutex()
static createMutex(key, options?): Promise<DistributedLock>;
Parameters
| Parameter | Type | Description |
|---|---|---|
key |
string |
Unique key for lock shared across options.scope |
options? |
LockOptions |
- |
Returns
Promise<DistributedLock>
Throws
When key is nil
Throws
When key is empty or longer than 256 characters
Example
Safely using a mutex by always releasing
// Ensure you import LockPowers
import { LockPowers } from "@andi/powers";
const mutex = await LockPowers.createMutex("my-lock");
try {
// may throw `LockAcquireTimeoutError`. To avoid errors, can also use `.tryAcquire()`
await mutex.acquire();
// critical code
} finally {
await mutex.release();
}
createSemaphore()
static createSemaphore(
key,
maxCount,
options?): Promise<DistributedSemaphore>;
No guaranteed fairness, but measures to minimize fairness issues in place.
Parameters
| Parameter | Type | Description |
|---|---|---|
key |
string |
Unique key for lock shared across options.scope. |
maxCount |
number |
Number of concurrent access |
options? |
LockOptions |
- |
Returns
Promise<DistributedSemaphore>
Throws
When key is nil
Throws
When key is empty or longer than 256 characters
Throws
When maxCount is not in range [1, 1024]
Throws
Various errors when options are invalid. See LockOptions for more information
Example
Safely using a semaphore by always releasing
// Ensure you import LockPowers
import { LockPowers } from "@andi/powers";
// acquire one of the 5 leases to my-shared-lock
const semaphore = await LockPowers.createSemaphore("my-shared-lock", 5);
try {
// may throw `LockAcquireTimeoutError`. To avoid errors, can also use `.tryAcquire()`
await semaphore.acquire();
// critical code with at most 5 concurrent processes
} finally {
await semaphore.release();
}
runExclusive()
static runExclusive<T>(
key,
callback,
options?): Promise<T>;
Fits most use cases. Acquires the lock, runs the provided callback, and then releases the lock.
For workflows that span multiple skills, be sure to pass LockScope.Organization as the scope argument for LockOptions.
Type Parameters
| Type Parameter |
|---|
T |
Parameters
| Parameter | Type | Description |
|---|---|---|
key |
string |
Unique key for lock shared across options.scope |
callback |
() => T | Promise<T> |
A function containing the callback to run exclusively |
options? |
LockOptions |
- |
Returns
Promise<T>
A promise that resolves with the result of the callback, or rejects if an error occurs
Example
In this example, the skill audits a business workflow managed by the skill. The workflow has been crafted where all updates and deletions to audit data are done through the "audit-update" lock.
Reads of audit data do not require a lock.
Additions of new audit do not require a lock if they are keyed uniquely with PowerHelpers.uuid() or another unique identifier.
In failure scenarios, the "audit-update" lock is naturally released after the expiration of the lockTimeout. Advanced handling of errors and results can help further refine
a critical business workflow.
// Ensure you import LockPowers
import { LockPowers, DataPowers } from "@andi/powers";
const exclusiveResult = await LockPowers.runExclusive("audit-update", async () => {
try {
const audits = await DataPowers.getOrCreateCollection<SkillAudit>(CollectionType.Dictionary, "skill-audits", { expiry: { expireMode: CollectionExpirationMode.Never } });
const auditToUpdate = await audits.get(auditId);
if (!auditToUpdate) return false;
auditToUpdate.status = false;
return true;
}
catch (err) {
return false;
}
});