Skip to content

Conversation

@muniter
Copy link
Owner

@muniter muniter commented Dec 1, 2025

Add a DistributedMutex interface for coordinating access across multiple processes, with implementations for all supported drivers:

  • RedisMutex: Uses SET NX PX for atomic lock acquisition with Lua script for safe release
  • SQLiteMutex: Uses dedicated locks table with unique constraint
  • MongooseMutex: Uses findOneAndUpdate with TTL index for auto-cleanup
  • FileMutex: Uses exclusive file creation (O_EXCL) with JSON lock files
  • InMemoryMutex: Map-based implementation for testing/single-process

Each implementation supports:

  • tryAcquire(key, ttlMs): Non-blocking lock acquisition
  • release(key, token): Token-based release to prevent wrong releases
  • withLock(key, fn, options): Convenience method with auto-retry

This provides the foundation for distributed coordination needed for cron job scheduling across multiple scheduler instances.


Note

Introduce a DistributedMutex interface with implementations for Redis, SQLite, Mongoose, File, and in-memory, plus comprehensive tests and updated package exports.

  • Mutex API:
    • Add interfaces/mutex with DistributedMutex, MutexLockOptions, and MutexAcquireError.
  • Drivers:
    • Add ./src/drivers/redis-mutex.ts using Redis SET NX PX and Lua-based safe release.
    • Add ./src/drivers/sqlite-mutex.ts using mutex_locks table and TTL cleanup.
    • Add ./src/drivers/mongoose-mutex.ts with mutex_locks collection, TTL index, and atomic updates.
    • Add ./src/drivers/file-mutex.ts using exclusive lock files in a lock directory.
    • Add ./src/drivers/memory-mutex.ts in-process Map-based mutex.
  • Tests:
    • Add tests/mutex/all-mutex.test.ts covering acquire, release, withLock, concurrency, and cleanup across all drivers using Testcontainers (Redis/Mongo), in-memory SQLite, and temp FS.
  • Packaging:
    • Update package.json exports to expose ./interfaces/mutex and mutex drivers under ./mutex/*.

Written by Cursor Bugbot for commit 23e0fc7. This will update automatically on new commits. Configure here.

Add a DistributedMutex interface for coordinating access across
multiple processes, with implementations for all supported drivers:

- RedisMutex: Uses SET NX PX for atomic lock acquisition with Lua
  script for safe release
- SQLiteMutex: Uses dedicated locks table with unique constraint
- MongooseMutex: Uses findOneAndUpdate with TTL index for auto-cleanup
- FileMutex: Uses exclusive file creation (O_EXCL) with JSON lock files
- InMemoryMutex: Map-based implementation for testing/single-process

Each implementation supports:
- tryAcquire(key, ttlMs): Non-blocking lock acquisition
- release(key, token): Token-based release to prevent wrong releases
- withLock(key, fn, options): Convenience method with auto-retry

This provides the foundation for distributed coordination needed for
cron job scheduling across multiple scheduler instances.
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

return false;
}
throw err;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: File mutex release has TOCTOU race condition

The release method has a time-of-check to time-of-use (TOCTOU) race condition. It first reads the lock file and checks if the token matches (line 132-136), then separately calls fs.unlink() (line 137). Between these non-atomic operations, another process could delete the expired lock and create a new one with a different token. The original process would then delete the new lock, violating mutual exclusion. The other mutex implementations (Redis, SQLite, Mongoose) correctly use atomic check-and-delete operations to prevent this exact scenario.

Fix in Cursor Fix in Web

// Sanitize key to be filesystem-safe
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, '_');
return path.join(this.lockDir, `${safeKey}.lock`);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Key sanitization causes different keys to collide

The getLockPath method sanitizes keys by replacing all non-alphanumeric characters (except _ and -) with underscores. This causes different logical keys to map to the same lock file, breaking mutex semantics. For example, "key:1", "key.1", "key/1", and "key_1" all become "key_1.lock". A process acquiring a lock on "resource:abc" would incorrectly block another process trying to lock "resource.abc", even though these are intended to be independent locks.

Fix in Cursor Fix in Web

- Rewrite tests to use testcontainers for all drivers (Redis, MongoDB)
- Add SQLiteMutex tests using in-memory database
- Test all 5 mutex implementations with comprehensive test suite
- Fix duplicate Mongoose index warning on expiresAt field
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants