diff --git a/CHANGES.md b/CHANGES.md index 73cc395..316198e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1 +1,6 @@ # Changes Logs + +## v0.2.0 + +- Added documents. +- Improved lock mechanism using lock-token. diff --git a/README.md b/README.md index 4b91d92..3e0ff92 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,14 @@ [![npm version](https://img.shields.io/npm/v/@litert/mutex.svg?colorB=brightgreen)](https://www.npmjs.com/package/@litert/mutex "Stable Version") [![License](https://img.shields.io/npm/l/@litert/mutex.svg?maxAge=2592000?style=plastic)](https://github.com/litert/mutex/blob/master/LICENSE) -[![node](https://img.shields.io/node/v/@litert/mutex.svg?colorB=brightgreen)](https://nodejs.org/dist/latest-v8.x/) [![GitHub issues](https://img.shields.io/github/issues/litert/mutex.js.svg)](https://github.com/litert/mutex.js/issues) [![GitHub Releases](https://img.shields.io/github/release/litert/mutex.js.svg)](https://github.com/litert/mutex.js/releases "Stable Release") -A mutex implement for Node.js. +A mutex implement for JavaScript. ## Requirement -- TypeScript v2.6.1 (or newer) -- Node.js v8.0.0 (or newer) +- TypeScript v2.9.2 (or newer) ## Installation @@ -19,6 +17,10 @@ A mutex implement for Node.js. npm i @litert/mutex --save ``` +## Document + +- [简体中文版](./docs/zh-CN/README.md) + ## License This library is published under [Apache-2.0](./LICENSE) license. diff --git a/docs/zh-CN/README.md b/docs/zh-CN/README.md new file mode 100644 index 0000000..25e9829 --- /dev/null +++ b/docs/zh-CN/README.md @@ -0,0 +1,7 @@ +# LiteRT/Mutex 文档 + +Mutex.js 库提供互斥量,用于多会话、多进程环境下,对资源的访问进行控制。 + +## 目录 + +- [API 文档](./apis/README.md) diff --git a/docs/zh-CN/apis/README.md b/docs/zh-CN/apis/README.md new file mode 100644 index 0000000..603ed38 --- /dev/null +++ b/docs/zh-CN/apis/README.md @@ -0,0 +1,10 @@ +# LiteRT/Mutex API 文档 + +## 目录 + +- [接口 IDriver](./interface.IDriver.md) +- [接口 IFactory](./interface.IFactory.md) +- [接口 IMutex](./interface.IMutex.md) +- [模块方法 createFactory](./method.createFactory.md) +- [模块方法 getDefaultFactory](./method.getDefaultFactory.md) +- [模块方法 createIntraprocessDriver](./method.createIntraprocessDriver.md) diff --git a/docs/zh-CN/apis/interface.IDriver.md b/docs/zh-CN/apis/interface.IDriver.md new file mode 100644 index 0000000..5e4495f --- /dev/null +++ b/docs/zh-CN/apis/interface.IDriver.md @@ -0,0 +1,38 @@ +# 接口 IDriver + +该接口描述一个驱动对象的基本方法。 + +```ts +interface IDriver { + + /** + * 尝试锁定一个互斥量。 + * + * @param key 互斥量的唯一名称。 + * @param token 互斥量的锁定状态标识,用于确认是由哪个对象锁定的。 + * @param expiringAt 互斥量的锁定有效时长。设置为 0 则永不过期。 + * 这是一个毫秒时间戳。 + */ + lock( + key: string, + token: string, + expiringAt: number + ): Promise | boolean; + + /** + * 根据锁定标识解锁一个互斥量。 + * + * @param key 互斥量的唯一名称。 + * @param token 互斥量的锁定状态标识,用于确认是由哪个对象锁定的。 + */ + unlock(key: string, token: string): Promise | boolean; + + /** + * 判断一个互斥量是否被(指定锁定标识的拥有者)锁定。 + * + * @param key 互斥量的唯一名称。 + * @param token 互斥量的锁定状态标识,用于确认是由哪个对象锁定的。 + */ + checkLocked(key: string, token: string): Promise | boolean; +} +``` diff --git a/docs/zh-CN/apis/interface.IFactory.md b/docs/zh-CN/apis/interface.IFactory.md new file mode 100644 index 0000000..6b01feb --- /dev/null +++ b/docs/zh-CN/apis/interface.IFactory.md @@ -0,0 +1,37 @@ +# 接口 IFactory + +该接口描述一个工厂对象的基本方法。 + +```ts +interface IFactory { + + /** + * 将一个驱动对象注册为一种互斥量类型。 + * + * @param name 类型名称 + * @param driver 驱动对象 + */ + registerType( + name: string, + driver: IDriver + ): this; + + /** + * 列出所有可用的互斥量类型名称。 + */ + listTypes(): string[]; + + /** + * 创建一个指定类型的互斥量对象。 + * + * @param type 互斥量类型 + * @param key 互斥量的唯一标识符 + * @param ttl 互斥量的锁定有效时长。设置为 0 则永不过期。默认值:0 (毫秒) + */ + createMutex( + type: string, + key: string, + ttl?: number + ): IMutex; +} +``` diff --git a/docs/zh-CN/apis/interface.IMutex.md b/docs/zh-CN/apis/interface.IMutex.md new file mode 100644 index 0000000..3d6f81d --- /dev/null +++ b/docs/zh-CN/apis/interface.IMutex.md @@ -0,0 +1,74 @@ +# 接口 IMutex + +该接口描述一个互斥量对象的基本操作接口。 + +每个互斥量通过一个 key 进行标识。而一个互斥量对象是对特定一个互斥量的控制器。多个对象 +可以通过同一个 key 对同一个互斥量进行控制。彼此之间的控制相互排斥。 + +```ts +interface IMutex { + + /** + * 获取当前对象互斥量的唯一名称。 + */ + readonly key: string; + + /** + * 当前对象对该互斥量的每次锁定有效时长,单位:毫秒。 + * + * 0 表示永久有效。 + */ + readonly ttl: number; + + /** + * 当前对象对该互斥量的锁定时间,这是一个毫秒时间戳。 + * + * 0 表示未锁定。 + */ + readonly lockedAt: number; + + /** + * 该互斥量的锁定状态过期时间,到这个时间则该互斥量的锁定状态自动解除。 + * + * 这是一个毫秒时间戳。 + * + * 0 表示未永不过期。 + */ + readonly expiringAt: number; + + /** + * 检查当前对象是否成功锁定了该互斥量。 + */ + isLocked(): boolean; + + /** + * 检查当前对象对该互斥量的锁定状态是否已经过期。 + */ + isExpired(): boolean; + + /** + * 重新检查当前对象对该互斥量的锁定状态。 + */ + recheckLocked(): Promise | boolean; + + /** + * 尝试锁定该互斥量。该接口是非阻塞的,一旦失败立即返回,而不是等待其他会话、进程 + * 释放锁。 + * + * 如果该互斥量此前已经被当前对象锁定了,则也会返回 false。 + * + * @param ttl 锁定状态的有效时长,该参数可以覆盖该互斥量对象的设置。如果设置为 + * 0 则互斥量的锁定状态永不过期。 + * 默认值:0 (毫秒) + */ + lock(ttl?: number): Promise | boolean; + + /** + * 解除当前对象对该互斥量的锁定。 + * + * 如果该互斥量不是被当前对象锁定的,则无法解除锁定,返回 false。 + */ + unlock(): Promise | boolean; +} + +``` \ No newline at end of file diff --git a/docs/zh-CN/apis/method.createFactory.md b/docs/zh-CN/apis/method.createFactory.md new file mode 100644 index 0000000..a2ec9dc --- /dev/null +++ b/docs/zh-CN/apis/method.createFactory.md @@ -0,0 +1,7 @@ +# 模块方法 createFactory + +该方法用于创建一个互斥量对象的工厂对象。 + +```ts +function createFactory(): IFactory; +``` diff --git a/docs/zh-CN/apis/method.createIntraprocessDriver.md b/docs/zh-CN/apis/method.createIntraprocessDriver.md new file mode 100644 index 0000000..a726fd3 --- /dev/null +++ b/docs/zh-CN/apis/method.createIntraprocessDriver.md @@ -0,0 +1,10 @@ +# 模块方法 createIntraprocessDriver + +该方法用于创建一个进程内(`Intraprocess`)的互斥量驱动对象。 + +> 每个 `Intraprocess` 对象彼此独立隔离,即不同的 `Intraprocess` 对象之间,可以有 +> 同名的互斥量,且互不干扰。 + +```ts +function createIntraprocessDriver(): IDriver; +``` diff --git a/docs/zh-CN/apis/method.getDefaultFactory.md b/docs/zh-CN/apis/method.getDefaultFactory.md new file mode 100644 index 0000000..df374bf --- /dev/null +++ b/docs/zh-CN/apis/method.getDefaultFactory.md @@ -0,0 +1,7 @@ +# 模块方法 getDefaultFactory + +该方法用于获取默认的互斥量对象的工厂对象。 + +```ts +function getDefaultFactory(): IFactory; +``` diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2ecdcf0..a83e0d9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,15 +1,9 @@ { "name": "@litert/mutex", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/node": { - "version": "8.10.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.26.tgz", - "integrity": "sha512-opk6bLLErLSwyVVJeSH5Ek7ZWOBSsN0JrvXTNVGLXLAXKB9xlTYajrplR44xVyMrmbut94H6uJ9jqzM/12jxkA==", - "dev": true - }, "typescript": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", diff --git a/package.json b/package.json index d7c5776..d6bf9c6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@litert/mutex", - "version": "0.1.0", - "description": "A mutex implement for Node.js.", + "version": "0.2.0", + "description": "A mutex implement for JavaScript.", "main": "./lib/index.js", "scripts": { "prepare": "npm run rebuild", @@ -29,10 +29,6 @@ "types": "./lib/index.d.ts", "typings": "./lib/index.d.ts", "devDependencies": { - "@types/node": "^8.10.26", "typescript": "^2.9.2" - }, - "engines": { - "node": ">=8.0.0" } } diff --git a/src/lib/Common.ts b/src/lib/Common.ts index c4d013e..e6d1e71 100644 --- a/src/lib/Common.ts +++ b/src/lib/Common.ts @@ -20,13 +20,13 @@ export interface IDriver { * Try locking up a mutex. * * @param key The key of mutex to be locked. - * @param lockedAt The locked-time of mutex. + * @param token The lock-token of mutex. * @param expiringAt The expiring-time of mutex. It never expires if * set to 0. Default: 0 (ms) */ lock( key: string, - lockedAt: number, + token: string, expiringAt: number ): Promise | boolean; @@ -34,17 +34,17 @@ export interface IDriver { * Unlock a mutex. * * @param key The key of mutex to be unlocked. - * @param lockedAt The time of mutex when it's locked. + * @param token The lock-token of mutex. */ - unlock(key: string, lockedAt: number): Promise | boolean; + unlock(key: string, token: string): Promise | boolean; /** * Check if a mutex is locked. * * @param key The key of mutex to be checked. - * @param lockedAt The time of mutex when it's locked. + * @param token The lock-token of mutex. */ - checkLocked(key: string, lockedAt: number): Promise | boolean; + checkLocked(key: string, token: string): Promise | boolean; } export interface IFactory { @@ -126,7 +126,8 @@ export interface IMutex { recheckLocked(): Promise | boolean; /** - * Try locking up a mutex. + * Try locking up a mutex. This is a non-blocking method. It would return + * immediately if the mutex is locked by others. * * Return false if this mutex is already locked (by anyone). * diff --git a/src/lib/Driver.Intraprocess.ts b/src/lib/Driver.Intraprocess.ts index 4868562..683fdab 100644 --- a/src/lib/Driver.Intraprocess.ts +++ b/src/lib/Driver.Intraprocess.ts @@ -20,16 +20,16 @@ class Lock { public expiringAt: number; - public lockedAt: number; + public token: string; public constructor( - lockedAt: number, + token: string, expiringAt: number ) { this.expiringAt = expiringAt; - this.lockedAt = lockedAt; + this.token = token; } public isExpired(): boolean { @@ -50,7 +50,7 @@ implements Com.IDriver { public lock( key: string, - lockedAt: number, + token: string, expiringAt: number ): boolean { @@ -61,21 +61,21 @@ implements Com.IDriver { return false; } - this._locks[key] = new Lock(lockedAt, expiringAt); + this._locks[key] = new Lock(token, expiringAt); return true; } public unlock( key: string, - lockedAt: number + token: string ): boolean { let lock = this._locks[key]; if (lock) { - if (lock.lockedAt === lockedAt) { + if (lock.token === token) { delete this._locks[key]; return true; @@ -91,7 +91,7 @@ implements Com.IDriver { public checkLocked( key: string, - lockedAt: number + token: string ): boolean { let lock = this._locks[key]; @@ -105,7 +105,7 @@ implements Com.IDriver { return false; } - if (lock.lockedAt === lockedAt) { + if (lock.token === token) { return true; } diff --git a/src/lib/Mutex.ts b/src/lib/Mutex.ts index c39d796..dc777aa 100644 --- a/src/lib/Mutex.ts +++ b/src/lib/Mutex.ts @@ -27,6 +27,8 @@ implements Com.IMutex { private _lockedAt: number; + private _token!: string; + private _expiringAt: number; public constructor( @@ -79,8 +81,7 @@ implements Com.IMutex { if (this.isExpired()) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); } else { @@ -90,6 +91,10 @@ implements Com.IMutex { this._lockedAt = Date.now(); + this._token = Math.floor( + Math.random() * 0xFFFFFFFF + ).toString().padStart(8, "0") + this._lockedAt; + if (ttl || this._ttl) { this._expiringAt = this._lockedAt + (ttl || this._ttl); @@ -101,7 +106,7 @@ implements Com.IMutex { const result = this._driver.lock( this._key, - this._lockedAt, + this._token, this._expiringAt ); @@ -109,8 +114,7 @@ implements Com.IMutex { if (!result) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); } return result; @@ -120,14 +124,20 @@ implements Com.IMutex { if (!realResult) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); } }); return result; } + private _cleanUp(): void { + + this._lockedAt = 0; + this._expiringAt = 0; + delete this._token; + } + public unlock(): Promise | boolean { if (!this._lockedAt) { @@ -137,20 +147,18 @@ implements Com.IMutex { if (this.isExpired()) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); return true; } - const result = this._driver.unlock(this._key, this._lockedAt); + const result = this._driver.unlock(this._key, this._token); if (typeof result === "boolean") { if (result) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); } return result; @@ -160,8 +168,7 @@ implements Com.IMutex { if (aResult) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); } }); @@ -177,20 +184,18 @@ implements Com.IMutex { if (this.isExpired()) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); return false; } - const result = this._driver.checkLocked(this._key, this._lockedAt); + const result = this._driver.checkLocked(this._key, this._token); if (typeof result === "boolean") { if (!result) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); } return result; @@ -200,8 +205,7 @@ implements Com.IMutex { if (!aResult) { - this._lockedAt = 0; - this._expiringAt = 0; + this._cleanUp(); } });