initial commit
This commit is contained in:
		
							
								
								
									
										175
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore | ||||
|  | ||||
| # Logs | ||||
|  | ||||
| logs | ||||
| _.log | ||||
| npm-debug.log_ | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| lerna-debug.log* | ||||
| .pnpm-debug.log* | ||||
|  | ||||
| # Caches | ||||
|  | ||||
| .cache | ||||
|  | ||||
| # Diagnostic reports (https://nodejs.org/api/report.html) | ||||
|  | ||||
| report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json | ||||
|  | ||||
| # Runtime data | ||||
|  | ||||
| pids | ||||
| _.pid | ||||
| _.seed | ||||
| *.pid.lock | ||||
|  | ||||
| # Directory for instrumented libs generated by jscoverage/JSCover | ||||
|  | ||||
| lib-cov | ||||
|  | ||||
| # Coverage directory used by tools like istanbul | ||||
|  | ||||
| coverage | ||||
| *.lcov | ||||
|  | ||||
| # nyc test coverage | ||||
|  | ||||
| .nyc_output | ||||
|  | ||||
| # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | ||||
|  | ||||
| .grunt | ||||
|  | ||||
| # Bower dependency directory (https://bower.io/) | ||||
|  | ||||
| bower_components | ||||
|  | ||||
| # node-waf configuration | ||||
|  | ||||
| .lock-wscript | ||||
|  | ||||
| # Compiled binary addons (https://nodejs.org/api/addons.html) | ||||
|  | ||||
| build/Release | ||||
|  | ||||
| # Dependency directories | ||||
|  | ||||
| node_modules/ | ||||
| jspm_packages/ | ||||
|  | ||||
| # Snowpack dependency directory (https://snowpack.dev/) | ||||
|  | ||||
| web_modules/ | ||||
|  | ||||
| # TypeScript cache | ||||
|  | ||||
| *.tsbuildinfo | ||||
|  | ||||
| # Optional npm cache directory | ||||
|  | ||||
| .npm | ||||
|  | ||||
| # Optional eslint cache | ||||
|  | ||||
| .eslintcache | ||||
|  | ||||
| # Optional stylelint cache | ||||
|  | ||||
| .stylelintcache | ||||
|  | ||||
| # Microbundle cache | ||||
|  | ||||
| .rpt2_cache/ | ||||
| .rts2_cache_cjs/ | ||||
| .rts2_cache_es/ | ||||
| .rts2_cache_umd/ | ||||
|  | ||||
| # Optional REPL history | ||||
|  | ||||
| .node_repl_history | ||||
|  | ||||
| # Output of 'npm pack' | ||||
|  | ||||
| *.tgz | ||||
|  | ||||
| # Yarn Integrity file | ||||
|  | ||||
| .yarn-integrity | ||||
|  | ||||
| # dotenv environment variable files | ||||
|  | ||||
| .env | ||||
| .env.development.local | ||||
| .env.test.local | ||||
| .env.production.local | ||||
| .env.local | ||||
|  | ||||
| # parcel-bundler cache (https://parceljs.org/) | ||||
|  | ||||
| .parcel-cache | ||||
|  | ||||
| # Next.js build output | ||||
|  | ||||
| .next | ||||
| out | ||||
|  | ||||
| # Nuxt.js build / generate output | ||||
|  | ||||
| .nuxt | ||||
| dist | ||||
|  | ||||
| # Gatsby files | ||||
|  | ||||
| # Comment in the public line in if your project uses Gatsby and not Next.js | ||||
|  | ||||
| # https://nextjs.org/blog/next-9-1#public-directory-support | ||||
|  | ||||
| # public | ||||
|  | ||||
| # vuepress build output | ||||
|  | ||||
| .vuepress/dist | ||||
|  | ||||
| # vuepress v2.x temp and cache directory | ||||
|  | ||||
| .temp | ||||
|  | ||||
| # Docusaurus cache and generated files | ||||
|  | ||||
| .docusaurus | ||||
|  | ||||
| # Serverless directories | ||||
|  | ||||
| .serverless/ | ||||
|  | ||||
| # FuseBox cache | ||||
|  | ||||
| .fusebox/ | ||||
|  | ||||
| # DynamoDB Local files | ||||
|  | ||||
| .dynamodb/ | ||||
|  | ||||
| # TernJS port file | ||||
|  | ||||
| .tern-port | ||||
|  | ||||
| # Stores VSCode versions used for testing VSCode extensions | ||||
|  | ||||
| .vscode-test | ||||
|  | ||||
| # yarn v2 | ||||
|  | ||||
| .yarn/cache | ||||
| .yarn/unplugged | ||||
| .yarn/build-state.yml | ||||
| .yarn/install-state.gz | ||||
| .pnp.* | ||||
|  | ||||
| # IntelliJ based IDEs | ||||
| .idea | ||||
|  | ||||
| # Finder (MacOS) folder config | ||||
| .DS_Store | ||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| # mutex-lock-redis | ||||
|  | ||||
| To install dependencies: | ||||
|  | ||||
| ```bash | ||||
| bun install | ||||
| ``` | ||||
|  | ||||
| To run: | ||||
|  | ||||
| ```bash | ||||
| bun run index.ts | ||||
| ``` | ||||
|  | ||||
| This project was created using `bun init` in bun v1.1.43. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. | ||||
							
								
								
									
										30
									
								
								biome.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								biome.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| { | ||||
| 	"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", | ||||
| 	"vcs": { | ||||
| 		"enabled": false, | ||||
| 		"clientKind": "git", | ||||
| 		"useIgnoreFile": false | ||||
| 	}, | ||||
| 	"files": { | ||||
| 		"ignoreUnknown": false, | ||||
| 		"ignore": [] | ||||
| 	}, | ||||
| 	"formatter": { | ||||
| 		"enabled": true, | ||||
| 		"indentStyle": "tab" | ||||
| 	}, | ||||
| 	"organizeImports": { | ||||
| 		"enabled": true | ||||
| 	}, | ||||
| 	"linter": { | ||||
| 		"enabled": true, | ||||
| 		"rules": { | ||||
| 			"recommended": true | ||||
| 		} | ||||
| 	}, | ||||
| 	"javascript": { | ||||
| 		"formatter": { | ||||
| 			"quoteStyle": "double" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										3
									
								
								index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| import { MutexLock } from "./src/MutexLock"; | ||||
|  | ||||
| export default MutexLock; | ||||
							
								
								
									
										18
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| { | ||||
| 	"name": "mutex-lock-redis", | ||||
| 	"module": "index.ts", | ||||
| 	"type": "module", | ||||
| 	"scripts": { | ||||
| 		"lint": "bunx biome check --write" | ||||
| 	}, | ||||
| 	"devDependencies": { | ||||
| 		"@biomejs/biome": "1.9.4", | ||||
| 		"@types/bun": "latest" | ||||
| 	}, | ||||
| 	"peerDependencies": { | ||||
| 		"typescript": "^5.0.0" | ||||
| 	}, | ||||
| 	"dependencies": { | ||||
| 		"redis": "^4.7.0" | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/MutexLock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/MutexLock.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import type { MutexOptions } from "./types"; | ||||
| import redis from "redis"; | ||||
|  | ||||
| type RedisClient = redis.RedisClientType< | ||||
| 	redis.RedisDefaultModules & redis.RedisModules, | ||||
| 	redis.RedisFunctions, | ||||
| 	redis.RedisScripts | ||||
| >; | ||||
|  | ||||
| export class MutexLock { | ||||
| 	redisClient: RedisClient; | ||||
| 	mutexOptions: MutexOptions; | ||||
|  | ||||
| 	constructor(redisClient: RedisClient, options: MutexOptions) { | ||||
| 		this.mutexOptions = options; | ||||
| 		this.redisClient = redisClient; | ||||
| 	} | ||||
|  | ||||
| 	static async create(options: MutexOptions) { | ||||
| 		const redisClient = await redis | ||||
| 			.createClient({ | ||||
| 				url: `redis://${options.redis.host}:${options.redis.port}`, | ||||
| 			}) | ||||
| 			.connect(); | ||||
|  | ||||
| 		return new MutexLock(redisClient, options); | ||||
| 	} | ||||
|  | ||||
| 	async obtainLock(lockName: string) { | ||||
| 		const lockIdentifier = `mutexlock:${lockName}`; | ||||
| 		const releaseFunc = async () => { | ||||
| 			await this.redisClient.del(lockIdentifier); | ||||
| 		}; | ||||
| 		 | ||||
| 		while (true) { | ||||
| 			const acquired = await this.redisClient.set(lockIdentifier, "1", { | ||||
| 				NX: true | ||||
| 			}); | ||||
| 			 | ||||
| 			if (acquired) { | ||||
| 				await this.redisClient.expire( | ||||
| 					lockIdentifier,  | ||||
| 					this.mutexOptions.mutex?.ttl || 60 | ||||
| 				); | ||||
| 				return releaseFunc; | ||||
| 			} | ||||
| 			await new Promise(resolve =>  | ||||
| 				setTimeout(resolve, this.mutexOptions.mutex?.checkInterval || 100) | ||||
| 			); | ||||
| 		} | ||||
| 	}} | ||||
							
								
								
									
										10
									
								
								src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| export type MutexOptions = { | ||||
|   redis: { | ||||
|     host: string; | ||||
|     port: number; | ||||
|   }; | ||||
|   mutex?: { | ||||
|     checkInterval?: number; | ||||
|     ttl: number; | ||||
|   }; | ||||
| }; | ||||
							
								
								
									
										30
									
								
								test/mutex.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								test/mutex.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import { test } from "bun:test"; | ||||
| import { MutexLock } from "../src/MutexLock"; | ||||
|  | ||||
| test( | ||||
| 	"redis mutex", | ||||
| 	async () => { | ||||
| 		const mutexLock = await MutexLock.create({ | ||||
| 			redis: { | ||||
| 				host: "127.0.0.1", | ||||
| 				port: 6379, | ||||
| 			}, | ||||
| 		}); | ||||
|  | ||||
| 		const testLock = async () => { | ||||
| 			const release = await mutexLock.obtainLock("test"); | ||||
| 			console.log("got lock"); | ||||
| 			try { | ||||
| 				await new Promise((res) => setTimeout(res, 3000)); | ||||
| 			} finally { | ||||
| 				await release(); | ||||
| 				console.log("released lock"); | ||||
| 			} | ||||
| 		}; | ||||
|  | ||||
| 		await Promise.all([testLock(), testLock(), testLock(), testLock()]); | ||||
| 	}, | ||||
| 	{ | ||||
| 		timeout: Number.POSITIVE_INFINITY, | ||||
| 	}, | ||||
| ); | ||||
							
								
								
									
										27
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| { | ||||
| 	"compilerOptions": { | ||||
| 		// Enable latest features | ||||
| 		"lib": ["ESNext", "DOM"], | ||||
| 		"target": "ESNext", | ||||
| 		"module": "ESNext", | ||||
| 		"moduleDetection": "force", | ||||
| 		"jsx": "react-jsx", | ||||
| 		"allowJs": true, | ||||
|  | ||||
| 		// Bundler mode | ||||
| 		"moduleResolution": "bundler", | ||||
| 		"allowImportingTsExtensions": true, | ||||
| 		"verbatimModuleSyntax": true, | ||||
| 		"noEmit": true, | ||||
|  | ||||
| 		// Best practices | ||||
| 		"strict": true, | ||||
| 		"skipLibCheck": true, | ||||
| 		"noFallthroughCasesInSwitch": true, | ||||
|  | ||||
| 		// Some stricter flags (disabled by default) | ||||
| 		"noUnusedLocals": false, | ||||
| 		"noUnusedParameters": false, | ||||
| 		"noPropertyAccessFromIndexSignature": false | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user