Skip to main content
Glama
mkXultra
by mkXultra
ConcurrencyTests.test.ts12.2 kB
import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { promises as fs } from 'fs'; import path from 'path'; import { LockService, LockTimeoutError } from '../../../src/services/LockService.js'; describe('File Locking Concurrency Tests', () => { let testDataDir: string; let lockService1: LockService; let lockService2: LockService; let lockService3: LockService; beforeEach(async () => { // Create temporary test directory testDataDir = path.join(process.cwd(), 'lock-test-' + Date.now()); await fs.mkdir(testDataDir, { recursive: true }); // Create multiple lock service instances to simulate concurrent access lockService1 = new LockService(testDataDir, 5000); // 5 second timeout lockService2 = new LockService(testDataDir, 5000); lockService3 = new LockService(testDataDir, 5000); }); afterEach(async () => { // Clean up test directory try { await fs.rm(testDataDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); describe('Basic File Locking', () => { it('should allow exclusive access to file', async () => { let service1Completed = false; let service2Started = false; let service2Completed = false; const promise1 = lockService1.withLock('test-file.txt', async () => { // Hold lock for 200ms await new Promise(resolve => setTimeout(resolve, 200)); service1Completed = true; // Service 2 should not have started yet expect(service2Started).toBe(false); return 'result1'; }); // Start second operation after small delay await new Promise(resolve => setTimeout(resolve, 50)); const promise2 = lockService2.withLock('test-file.txt', async () => { service2Started = true; // Service 1 should be completed by now expect(service1Completed).toBe(true); service2Completed = true; return 'result2'; }); const [result1, result2] = await Promise.all([promise1, promise2]); expect(result1).toBe('result1'); expect(result2).toBe('result2'); expect(service1Completed).toBe(true); expect(service2Completed).toBe(true); }); it('should handle multiple concurrent lock requests', async () => { const results: string[] = []; const operations = []; for (let i = 1; i <= 5; i++) { operations.push( lockService1.withLock('shared-file.txt', async () => { // Small delay to ensure ordering matters await new Promise(resolve => setTimeout(resolve, 50)); results.push(`operation-${i}`); return `result-${i}`; }) ); } const allResults = await Promise.all(operations); // All operations should complete expect(allResults).toHaveLength(5); expect(results).toHaveLength(5); // Results should contain all operations (order may vary due to execution) for (let i = 1; i <= 5; i++) { expect(results).toContain(`operation-${i}`); expect(allResults).toContain(`result-${i}`); } }); it('should handle lock timeout', async () => { // Create a lock service with very short timeout const shortTimeoutService = new LockService(testDataDir, 100); // 100ms timeout let longOperationStarted = false; // Start long-running operation const longOperation = lockService1.withLock('timeout-test.txt', async () => { longOperationStarted = true; await new Promise(resolve => setTimeout(resolve, 500)); // 500ms operation return 'long-result'; }); // Wait for long operation to start await new Promise(resolve => setTimeout(resolve, 50)); expect(longOperationStarted).toBe(true); // Try to acquire lock with short timeout - should fail await expect( shortTimeoutService.withLock('timeout-test.txt', async () => { return 'short-result'; }) ).rejects.toThrow(LockTimeoutError); // Long operation should still complete successfully const longResult = await longOperation; expect(longResult).toBe('long-result'); }); }); describe('File Operations with Locking', () => { it('should safely handle concurrent file writes', async () => { const filePath = 'concurrent-writes.txt'; const writers = []; // Multiple services writing to the same file for (let i = 1; i <= 10; i++) { const serviceIndex = i % 3; // Rotate between 3 services const service = [lockService1, lockService2, lockService3][serviceIndex]; writers.push( service.withLock(filePath, async () => { // Read current content const currentContent = await service.readFile(filePath); // Append new line const newContent = currentContent + `Line ${i}\n`; // Small delay to simulate processing await new Promise(resolve => setTimeout(resolve, 10)); // Write back await service.writeFile(filePath, newContent); return i; }) ); } const results = await Promise.all(writers); // Verify all writes completed expect(results).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); // Verify final file content const finalContent = await lockService1.readFile(filePath); const lines = finalContent.trim().split('\n'); expect(lines).toHaveLength(10); for (let i = 1; i <= 10; i++) { expect(lines).toContain(`Line ${i}`); } }); it('should handle concurrent file appends', async () => { const filePath = 'append-test.jsonl'; const appenders = []; for (let i = 1; i <= 20; i++) { appenders.push( lockService1.withLock(filePath, async () => { const message = JSON.stringify({ id: `msg-${i}`, content: `Message ${i}`, timestamp: new Date().toISOString() }); await lockService1.appendFile(filePath, message + '\n'); return i; }) ); } await Promise.all(appenders); // Verify all messages were appended const content = await lockService1.readFile(filePath); const lines = content.trim().split('\n'); expect(lines).toHaveLength(20); // Parse each line to verify JSON format for (let i = 0; i < lines.length; i++) { const parsed = JSON.parse(lines[i]); expect(parsed.id).toMatch(/^msg-\d+$/); expect(parsed.content).toMatch(/^Message \d+$/); expect(parsed.timestamp).toBeDefined(); } }); }); describe('Lock Service Utilities', () => { it('should create directories safely', async () => { const operations = []; // Multiple services trying to create the same directory structure for (let i = 1; i <= 5; i++) { operations.push( lockService1.ensureDir(`rooms/room-${i}/subdirectory`) ); } await Promise.all(operations); // Verify all directories were created for (let i = 1; i <= 5; i++) { const fullPath = path.join(testDataDir, `rooms/room-${i}/subdirectory`); const stats = await fs.stat(fullPath); expect(stats.isDirectory()).toBe(true); } }); it('should handle file existence checks', async () => { const filePath = 'existence-test.txt'; // Initially file should not exist expect(await lockService1.fileExists(filePath)).toBe(false); // Create file await lockService1.writeFile(filePath, 'test content'); // Now it should exist expect(await lockService1.fileExists(filePath)).toBe(true); expect(await lockService2.fileExists(filePath)).toBe(true); expect(await lockService3.fileExists(filePath)).toBe(true); // Delete file await lockService1.deleteFile(filePath); // Should not exist anymore expect(await lockService1.fileExists(filePath)).toBe(false); }); it('should list files correctly', async () => { const dirPath = 'list-test'; // Create some files await lockService1.writeFile(`${dirPath}/file1.txt`, 'content1'); await lockService1.writeFile(`${dirPath}/file2.txt`, 'content2'); await lockService1.writeFile(`${dirPath}/file3.json`, '{}'); const files = await lockService1.listFiles(dirPath); expect(files).toHaveLength(3); expect(files).toContain('file1.txt'); expect(files).toContain('file2.txt'); expect(files).toContain('file3.json'); }); }); describe('Error Handling in Concurrent Environment', () => { it('should handle errors gracefully without blocking other operations', async () => { const results: Array<'success' | 'error'> = []; const operations = [ // Successful operation lockService1.withLock('error-test.txt', async () => { await new Promise(resolve => setTimeout(resolve, 50)); results.push('success'); return 'success-1'; }), // Operation that throws error lockService2.withLock('error-test.txt', async () => { await new Promise(resolve => setTimeout(resolve, 50)); results.push('error'); throw new Error('Intentional error'); }), // Another successful operation lockService3.withLock('error-test.txt', async () => { await new Promise(resolve => setTimeout(resolve, 50)); results.push('success'); return 'success-2'; }) ]; const settled = await Promise.allSettled(operations); // One should fail, two should succeed expect(settled[0].status).toBe('fulfilled'); expect(settled[1].status).toBe('rejected'); expect(settled[2].status).toBe('fulfilled'); // All operations should have run expect(results).toHaveLength(3); expect(results.filter(r => r === 'success')).toHaveLength(2); expect(results.filter(r => r === 'error')).toHaveLength(1); }); it('should clean up locks properly on errors', async () => { // Operation that throws error await expect( lockService1.withLock('cleanup-test.txt', async () => { throw new Error('Test error'); }) ).rejects.toThrow('Test error'); // Subsequent operation should not be blocked const result = await lockService2.withLock('cleanup-test.txt', async () => { return 'cleanup-success'; }); expect(result).toBe('cleanup-success'); }); }); describe('Performance under Concurrency', () => { it('should handle high concurrency without deadlocks', async () => { const numOperations = 30; // Reduced from 50 to prevent timeout const operations = []; for (let i = 0; i < numOperations; i++) { const service = [lockService1, lockService2, lockService3][i % 3]; operations.push( service.withLock(`file-${i % 5}.txt`, async () => { // Reduced delay to speed up test await new Promise(resolve => setTimeout(resolve, Math.random() * 5 + 1)); return i; }) ); } const startTime = Date.now(); const results = await Promise.all(operations); const endTime = Date.now(); // All operations should complete expect(results).toHaveLength(numOperations); expect(results.sort((a, b) => a - b)).toEqual( Array.from({ length: numOperations }, (_, i) => i) ); // Should complete in reasonable time (less than 10 seconds) expect(endTime - startTime).toBeLessThan(10000); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mkXultra/agent-communication-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server