Skip to content

Commit 1c463e4

Browse files
committed
Bump version to v1.1.3: Add resilience test, improve README, implementation refinement
1 parent c9745ad commit 1c463e4

10 files changed

+249
-24
lines changed

README.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ The `ZeroOverheadLock` class implements a modern Promise-lock for Node.js projec
1414
* [Getter Methods](#getter-methods)
1515
* [Opt for Atomic Operations When Working Against External Resources](#opt-atomic-operations)
1616
* [Using Locks as a Semaphore with a Concurrency of 1](#lock-as-semaphore)
17-
* [Use Case Example: Aggregating Intrusion Detection Event Logs](#use-case-example)
17+
* [Use Case Example: Aggregating Intrusion Detection Event Logs](#first-use-case-example)
18+
* [Check-and-Abort Example: Non-Overlapping Recurring Task](#second-use-case-example)
1819
* [License](#license)
1920

2021
## Key Features :sparkles:<a id="key-features"></a>
@@ -98,7 +99,7 @@ By combining the read and update into a single atomic operation, the code avoids
9899

99100
In scenarios where performance considerations require controlling access, in-memory locks can be useful. For example, limiting concurrent access to a shared resource may be necessary to reduce contention or meet operational constraints. In such cases, locks are employed as a semaphore with a concurrency limit of 1, ensuring that no more than one operation is executed at a time.
100101

101-
## Use Case Example: Aggregating Intrusion Detection Event Logs :shield:<a id="use-case-example"></a>
102+
## Use Case Example: Aggregating Intrusion Detection Event Logs :shield:<a id="first-use-case-example"></a>
102103

103104
In an Intrusion Detection System (IDS), it is common to aggregate non-critical alerts (e.g., low-severity anomalies) in memory and flush them to a database in bulk. This approach minimizes the load caused by frequent writes for non-essential data. The bulk writes occur either periodically or whenever the accumulated data reaches a defined threshold.
104105

@@ -227,6 +228,53 @@ export class IntrusionDetectionSystem {
227228
* __Improved Throughput__: In-memory accumulation remains active while bulk writes occur, reducing backpressure.
228229
* __Self-Throttling__: Prevents multiple simultaneous bulk writes while enabling continuous alert ingestion.
229230

231+
## Check-and-Abort Example: Non-Overlapping Recurring Task :repeat_one:<a id="second-use-case-example"></a>
232+
233+
Consider a **non-overlapping** variant of `setInterval`, designed for asynchronous tasks:
234+
A scheduler component that manages a single recurring task while **ensuring executions do not overlap**. The scheduler maintains a fixed interval between start times, and if a previous execution is still in progress when a new cycle begins, the new execution is skipped.
235+
Additionally, the component supports graceful teardown, meaning it not only stops future executions but also awaits the completion of any ongoing execution before shutting down.
236+
237+
The `isAvailable` lock indicator can be used to determine whether an execution should be skipped:
238+
```ts
239+
import { ZeroOverheadLock } from 'zero-overhead-promise-lock';
240+
241+
export class NonOverlappingRecurringTask {
242+
private readonly _lock = new ZeroOverheadLock<void>();
243+
private _timerHandle?: ReturnType<typeof setInterval>;
244+
245+
constructor(
246+
private readonly _task: () => Promise<void>,
247+
private readonly _intervalMs: number
248+
) {}
249+
250+
public start(): void {
251+
if (this._timerHandle !== undefined) {
252+
throw new Error('Instance is already running');
253+
}
254+
255+
this._timerHandle = setInterval(
256+
(): void => {
257+
if (this._lock.isAvailable) {
258+
// For simplicity, we assume the task does not throw.
259+
this._lock.executeExclusive(this._task);
260+
}
261+
},
262+
this._intervalMs
263+
);
264+
}
265+
266+
public async stop(): Promise<void> {
267+
if (this._timerHandle === undefined) {
268+
return;
269+
}
270+
271+
clearInterval(this._timerHandle);
272+
this._timerHandle = undefined;
273+
await this._lock.waitForAllExistingTasksToComplete();
274+
}
275+
}
276+
```
277+
230278
## License :scroll:<a id="license"></a>
231279

232280
[Apache 2.0](LICENSE)

dist/zero-overhead-promise-lock.d.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,12 @@ export type AsyncTask<T> = () => Promise<T>;
4848
* or maintaining a clear state between unit tests.
4949
*/
5050
export declare class ZeroOverheadLock<T> {
51-
private _currentlyExecutingTask;
5251
private _pendingTasksCount;
5352
/**
5453
* Availability indicator:
5554
* A pending `_waitForAvailability` promise signifies that the lock is currently held.
5655
* Its resolve function is used to notify all awaiters of a state change. This approach
5756
* has similarities with a condition_variable in C++.
58-
*
59-
* Notably, this promise never rejects, which is a key distinction from `_currentlyExecutingTask`.
6057
*/
6158
private _waitForAvailablity?;
6259
private _notifyTaskCompletion?;
@@ -125,6 +122,8 @@ export declare class ZeroOverheadLock<T> {
125122
* all tasks - whether already executing or queued - are fully processed before proceeding.
126123
* Examples include application shutdowns (e.g., `onModuleDestroy` in Nest.js applications)
127124
* or maintaining a clear state between unit tests.
125+
* This need is especially relevant in Kubernetes ReplicaSet deployments. When an HPA controller
126+
* scales down, pods begin shutting down gracefully.
128127
*
129128
* ### Graceful Shutdown
130129
* The returned promise only accounts for tasks registered at the time this method is called.

dist/zero-overhead-promise-lock.js

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/zero-overhead-promise-lock.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/zero-overhead-promise-lock.test.js

Lines changed: 87 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)