Skip to content

Pessimistic Lock in Hazelcast Node.js Client Fails to Ensure Data Consistency #1564

@NataliaGon

Description

@NataliaGon

Describe the bug

When using pessimistic locking (map.lock() / map.unlock()) in the Hazelcast Node.js client, concurrent updates to a shared counter result in lost increments.
Despite the expectation that locking should ensure exclusive access to the key across all threads, multiple clients appear to perform overlapping updates.

This leads to an incorrect final counter value — significantly smaller than expected — which suggests that the Node.js client’s implementation of map.lock() does not fully guarantee atomicity under concurrency.

Expected behavior
await map.lock(key);
const value = await map.get(key);
await map.put(key, value + 1);
await map.unlock(key);

no other client should be able to acquire the lock for the same key until it is released.
After 10 threads increment the same counter key 10 000 times each, the expected final value is:

10 threads × 10 000 increments = 100 000

Actual behavior

The test consistently produces incorrect results such as:

map-pessimistic: 6.871s
Final (pessimistic): 12 210 — expected 100 000

Even though the lock() and unlock() calls succeed, it appears that multiple asynchronous operations overlap during the network round-trip, breaking exclusive access guarantees.

Environment (please complete the following information):

  1. Exact Node.js client version that you use (_e.g 4.1.0): 5.3.0

  2. Exact Hazelcast version that you use (e.g. 4.0.1, also whether it is a minor release, or the latest snapshot): 5.4.0

  3. Cluster size, i.e. the number of Hazelcast cluster members: 3

  4. Number of clients: 1

  5. Java version. It is also helpful to mention the JVM parameters: Open JDK17, inside Doker

  6. Node.js version. It is also useful to mention node command line arguments if there are any: 20.19.5

  7. Operating system. If it is Linux, kernel version is helpful: macOS 10.15.7 host with Docker Desktop (Linux 5.15 kernel)

To Reproduce

const map = await client.getMap("counters");
await map.put("counter", 0);

await Promise.all(
Array.from({ length: 10 }, async () => {
for (let i = 0; i < 10000; i++) {
await map.lock("counter");
try {
const v = await map.get("counter");
await map.put("counter", v + 1);
} finally {
await map.unlock("counter");
}
}
})
);

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions