https://github.com/izure1/ryoiki
A range-based locking library for JavaScript, enabling concurrent read locks and exclusive write locks with seamless management of overlapping ranges.
https://github.com/izure1/ryoiki
async-lock concurrency javascript lock locking range-lock typescript
Last synced: 2 months ago
JSON representation
A range-based locking library for JavaScript, enabling concurrent read locks and exclusive write locks with seamless management of overlapping ranges.
- Host: GitHub
- URL: https://github.com/izure1/ryoiki
- Owner: izure1
- License: mit
- Created: 2024-11-19T12:40:50.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-02-28T14:34:20.000Z (about 1 year ago)
- Last Synced: 2025-10-09T15:08:59.226Z (7 months ago)
- Topics: async-lock, concurrency, javascript, lock, locking, range-lock, typescript
- Language: TypeScript
- Homepage:
- Size: 73.2 KB
- Stars: 1
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Ryoiki
[](https://www.jsdelivr.com/package/npm/ryoiki)

`Ryoiki` is a JavaScript mutex library that provides read/write locks based on specific ranges.
This library helps maintain consistency and stability by controlling concurrent access to data.
This library supports **Async** and **TypeScript**.
## 📦 Installation
```bash
npm install ryoiki
```
or
```typescript
import { Ryoiki } from 'https://cdn.jsdelivr.net/npm/ryoiki@1/+esm'
```
## 🚀 Usage
### Basic Example
```typescript
import { Ryoiki } from 'ryoiki'
const ryoiki = new Ryoiki()
let lockId: string; // Store lock ID externally
await ryoiki.readLock([0, 10], async (_lockId) => {
lockId = _lockId
console.log('Reading from range [0, 10]')
}).finally(() => ryoiki.readUnlock(lockId)) // Always unlock
```
### Key Concepts
#### 1. **Default Lock Range**
- If the first parameter of `readLock` or `writeLock` is omitted, it defaults to `[-Infinity, Infinity]`.
- Example:
```typescript
let lockId: string
await ryoiki.readLock(async (_lockId) => {
lockId = _lockId
console.log('Read lock applied to the entire range')
}).finally(() => ryoiki.readUnlock(lockId))
```
#### 2. **Lock Waiting Behavior**
- **Read Lock**:
- Can execute immediately if overlapping with other read locks.
- Waits if overlapping with a write lock.
- **Write Lock**:
- Waits if overlapping with other read or write locks.
#### 3. **Timeout Behavior**
- Both `readLock` and `writeLock` now support an optional `timeout` parameter.
- **Timeout**: The lock request will wait for the specified time in milliseconds before throwing an error if the lock cannot be acquired.
- Defaults to `Infinity`, meaning it will wait indefinitely unless otherwise specified.
- If the lock cannot be acquired within the given `timeout` period, a timeout error is thrown.
#### 4. **Unlocking**
- Always use `finally` to release locks, even if an error occurs in the callback.
- Correct Usage:
```typescript
let lockId: string
await ryoiki.writeLock([0, 10], async (_lockId) => {
lockId = _lockId
throw new Error('Exception occurred')
}).finally(() => ryoiki.writeUnlock(lockId)) // Always unlock
```
#### 5. **Locks, Deadlocks, and Caution**
- **`readLock`** and **`writeLock`** are used to manage access to data by locking specific ranges.
- A **read lock** allows multiple readers but waits if a write lock exists.
- A **write lock** prevents both read and write locks in the same range, ensuring exclusive access.
- **Deadlock** occurs when two or more processes are unable to proceed because each is waiting for the other to release a lock.
In the context of `Ryoiki`, this could happen if:
- A `readLock` is waiting for a `writeLock` to release, and the `writeLock` is waiting for a `readLock` to release.
To prevent deadlocks:
- Ensure that locks are always released as soon as they are no longer needed.
- Use `timeout` to avoid waiting indefinitely.
- For a deeper understanding of these concepts, you can refer to these Wikipedia articles:
- [Read-Write Lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock)
- [Deadlock](https://en.wikipedia.org/wiki/Deadlock)
## 📖 API
### `readLock(range?: [number, number], callback: (lockId: string) => Promise, timeout?: number): Promise`
- **Description**: Requests a read lock for the specified range.
- **Parameters**:
- `range`: Range to lock as `[start, end]`. Defaults to `[-Infinity, Infinity]`.
- `callback`: Async function executed after lock is acquired.
- `lockId`: Unique ID for the current lock.
- `timeout`: Optional timeout in milliseconds. If the lock cannot be acquired within the specified time, the operation will throw a timeout error.
- Defaults to `undefined` (wait indefinitely).
- **Returns**: The result of the callback function.
### `writeLock(range?: [number, number], callback: (lockId: string) => Promise, timeout?: number): Promise`
- **Description**: Requests a write lock for the specified range.
- **Parameters**:
- `range`: Range to lock as `[start, end]`. Defaults to `[-Infinity, Infinity]`.
- `callback`: Async function executed after lock is acquired.
- `lockId`: Unique ID for the current lock.
- `timeout`: Optional timeout in milliseconds. If the lock cannot be acquired within the specified time, the operation will throw a timeout error.
- Defaults to `undefined` (wait indefinitely).
- **Returns**: The result of the callback function.
### `readUnlock(lockId: string): void`
- **Description**: Releases a read lock.
### `writeUnlock(lockId: string): void`
- **Description**: Releases a write lock.
### `range(start: number, length: number): [number, number]`
- **Description**: Creates a tuple `[start, start + length]` based on the given start and length.
- **Usage Example**:
```typescript
const r = ryoiki.range(0, 10) // [0, 10]
const s = ryoiki.range(5, 10) // [5, 15]
```
## 🌟 Examples
### Read and Write Operations with Timeout
```typescript
const ryoiki = new Ryoiki()
let lockId: string
// Read Lock with timeout
await ryoiki.readLock([0, 10], async (_lockId) => {
lockId = _lockId
console.log('Reading from range [0, 10]')
}, 1000).finally(() => ryoiki.readUnlock(lockId)) // Always unlock
// Write Lock with timeout
await ryoiki.writeLock([5, 15], async (_lockId) => {
lockId = _lockId
console.log('Writing to range [5, 15]')
}, 1000).finally(() => ryoiki.writeUnlock(lockId)) // Always unlock
```
## 📜 License
MIT License. Feel free to use and contribute! 🙌