Skip to content

Commit

Permalink
Bump version to v1.1.0: Add backpressure metric for resource manageme…
Browse files Browse the repository at this point in the history
…nt decisions
  • Loading branch information
ori88c committed Feb 8, 2025
1 parent 3ff5d5e commit 7990093
Show file tree
Hide file tree
Showing 10 changed files with 171 additions and 20 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ If your use case requires a concurrency greater than 1, consider using the semap
## Key Features :sparkles:<a id="key-features"></a>

- __Mutual Exclusiveness :lock:__: Ensures the mutually exclusive execution of asynchronous tasks, either to prevent potential race conditions caused by tasks spanning across multiple event-loop iterations, or for performance optimization.
- __Graceful Termination :hourglass_flowing_sand:__: Await the completion of all currently pending and executing tasks using the `waitForAllExistingTasksToComplete` method. Example use cases include application shutdowns (e.g., `onModuleDestroy` in Nest.js applications) or maintaining a clear state between unit-tests.
- __Graceful Teardown :hourglass_flowing_sand:__: Await the completion of all currently pending and executing tasks using the `waitForAllExistingTasksToComplete` method. Example use cases include application shutdowns (e.g., `onModuleDestroy` in Nest.js applications) or maintaining a clear state between unit-tests.
- __Suitable for "check and abort" scenarios__: The `isAvailable` getter indicator enables to skip or abort operations if the lock is currently held by another task.
- __Backpressure Metric :bar_chart:__: The `pendingTasksCount` getter provides a real-time metric indicating the current backpressure from tasks waiting for the lock to become available. Users can leverage this data to make informed decisions, such as throttling, load balancing, or managing system load. Additionally, this metric can aid in **internal resource management** within a containerized environment. If multiple locks exist - each tied to a unique key - a backpressure value of 0 may indicate that a lock is no longer needed and can be removed temporarily to optimize resource usage.
- __High Efficiency :gear:__: Leverages the Node.js microtasks queue to serve tasks in FIFO order, eliminating the need for manually managing an explicit queue of pending tasks.
- __Comprehensive documentation :books:__: The class is thoroughly documented, enabling IDEs to provide helpful tooltips that enhance the coding experience.
- __Tests :test_tube:__: Fully covered by extensive unit tests.
Expand All @@ -51,9 +52,10 @@ If needed, refer to the code documentation for a more comprehensive description

## Getter Methods :mag:<a id="getter-methods"></a>

The `ZeroOverheadLock` class provides the following getter method to reflect the current lock's state:
The `ZeroOverheadLock` class provides the following getter methods to reflect the current lock's state:

* __isAvailable__: Indicates whether the lock is currently available to immediately begin executing a new task. This property is particularly useful in "check and abort" scenarios, where an operation should be **skipped or aborted** if the lock is currently held by another task.
* __pendingTasksCount__: Returns the number of tasks that are currently pending execution due to the lock being held. These tasks are waiting for the lock to become available before they can proceed.

## Opt for Atomic Operations When Working Against External Resources :key:<a id="opt-atomic-operations"></a>

Expand Down Expand Up @@ -153,7 +155,7 @@ export class IntrusionDetectionSystem {
* This method is well-suited for use in `onModuleDestroy` in Nest.js
* applications or similar lifecycle scenarios.
*/
public async onDestroy(): Promise<void> {
public async onTeardown(): Promise<void> {
while (!this._accumulationLock.isAvailable) {
await this._accumulationLock.waitForAllExistingTasksToComplete();
}
Expand Down Expand Up @@ -200,7 +202,7 @@ export class IntrusionDetectionSystem {
* This method is well-suited for use in `onModuleDestroy` in Nest.js
* applications or similar lifecycle scenarios.
*/
public async onDestroy(): Promise<void> {
public async onTeardown(): Promise<void> {
while (!this._bulkWriteLock.isAvailable) {
await this._bulkWriteLock.waitForAllExistingTasksToComplete();
}
Expand Down
32 changes: 32 additions & 0 deletions dist/zero-overhead-promise-lock.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export type AsyncTask<T> = () => Promise<T>;
*/
export declare class ZeroOverheadLock<T> {
private _currentlyExecutingTask;
private _pendingTasksCount;
/**
* isAvailable
*
Expand All @@ -58,6 +59,37 @@ export declare class ZeroOverheadLock<T> {
* @returns `true` if no task is currently executing; otherwise, `false`.
*/
get isAvailable(): boolean;
/**
* pendingTasksCount
*
* Returns the number of tasks that are currently pending execution due to the lock being held.
* These tasks are waiting for the lock to become available before they can proceed.
*
* ### Monitoring Backpressure
* This property is useful for monitoring backpressure and making informed decisions, such as
* dynamically adjusting task submission rates or triggering alerts if the backpressure grows
* too large. Additionally, this metric can aid in internal resource management within a
* containerized environment.
*
* ### Real-World Example: A Keyed Lock for Batch Processing of Kafka Messages
* Suppose you are consuming a batch of Kafka messages from the same partition concurrently, but
* need to ensure sequential processing for messages associated with the same key. For example,
* each message may represent an action on a user account, where processing multiple actions
* concurrently could lead to race conditions. Kafka experts might suggest increasing the number
* of partitions to ensure sequential processing per partition. However, in practice, this approach
* can be costly. As a result, it is not uncommon to prefer batch-processing messages from the same
* partition rather than increasing the partition count.
* To prevent concurrent processing of same-key messages during batch processing, you can use this
* lock as a building block for a Keyed Lock, where each **key** is mapped to its own lock instance.
* In this case, the key could be the UserID, ensuring that actions on the same user account are
* processed sequentially.
* When multiple locks exist - each associated with a unique key - the `pendingTasksCount` metric
* can help optimize resource usage. Specifically, if a lock’s backpressure reaches 0, it may indicate
* that the lock is no longer needed and can be **removed** from the Keyed Lock to free up resources.
*
* @returns The number of tasks currently waiting for execution.
*/
get pendingTasksCount(): number;
/**
* executeExclusive
*
Expand Down
38 changes: 38 additions & 0 deletions dist/zero-overhead-promise-lock.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/zero-overhead-promise-lock.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 23 additions & 5 deletions dist/zero-overhead-promise-lock.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7990093

Please sign in to comment.