238 lines
7.3 KiB
JavaScript
238 lines
7.3 KiB
JavaScript
const NamedPipeLockServer = require('../lock.namedpipe');
|
|
const NamedPipeRWLock = require('../lock-client.namedpipe');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
|
|
jest.setTimeout(3000000);
|
|
// Helper function to create a unique pipe path for testing
|
|
const createTestPipePath = () => {
|
|
if (process.platform === 'win32') {
|
|
return `\\\\.\\pipe\\rwlock-test-${Date.now()}`;
|
|
} else {
|
|
return path.join(os.tmpdir(), `rwlock-test-${Date.now()}.sock`);
|
|
}
|
|
};
|
|
|
|
describe('NamedPipeLock', () => {
|
|
let server;
|
|
let pipePath;
|
|
let connect = {}
|
|
|
|
// 在所有测试之前启动服务器
|
|
beforeAll(async () => {
|
|
pipePath = createTestPipePath();
|
|
connect = {pipePath}
|
|
server = new NamedPipeLockServer({pipePath});
|
|
await server.start();
|
|
});
|
|
|
|
// 在所有测试完成后停止服务器
|
|
afterAll(() => {
|
|
server.stop();
|
|
// 清理管道文件 (仅限Unix系统)
|
|
if (process.platform !== 'win32' && fs.existsSync(pipePath)) {
|
|
fs.unlinkSync(pipePath);
|
|
}
|
|
});
|
|
|
|
describe('Basic Lock Operations', () => {
|
|
test('should acquire and release read lock without waiting when not locked', async () => {
|
|
const lock = new NamedPipeRWLock('resource1',{connect});
|
|
|
|
const startTime = Date.now();
|
|
await lock.readLock();
|
|
const lockAcquiredTime = Date.now();
|
|
|
|
// 应该几乎立即获得锁(无需等待)
|
|
expect(lockAcquiredTime - startTime).toBeLessThan(100);
|
|
|
|
await lock.unlock();
|
|
lock.close();
|
|
});
|
|
|
|
test('should acquire and release write lock without waiting when not locked', async () => {
|
|
const lock = new NamedPipeRWLock('resource2',{connect});
|
|
|
|
const startTime = Date.now();
|
|
await lock.writeLock();
|
|
const lockAcquiredTime = Date.now();
|
|
|
|
// 应该几乎立即获得锁(无需等待)
|
|
expect(lockAcquiredTime - startTime).toBeLessThan(100);
|
|
|
|
await lock.unlock();
|
|
lock.close();
|
|
});
|
|
|
|
test('should allow consecutive acquisitions and releases without queueing', async () => {
|
|
const lock = new NamedPipeRWLock('resource3',{connect});
|
|
|
|
// 第一次获取
|
|
await lock.readLock();
|
|
expect(lock.isLocked).toBe(true);
|
|
await lock.unlock();
|
|
|
|
// 第二次获取
|
|
await lock.writeLock();
|
|
expect(lock.isLocked).toBe(true);
|
|
await lock.unlock();
|
|
|
|
// 第三次获取
|
|
await lock.readLock();
|
|
expect(lock.isLocked).toBe(true);
|
|
await lock.unlock();
|
|
|
|
lock.close();
|
|
});
|
|
});
|
|
|
|
describe('Multiple Clients', () => {
|
|
test('should handle multiple concurrent read locks', async () => {
|
|
const lock1 = new NamedPipeRWLock('sharedResource',{connect});
|
|
const lock2 = new NamedPipeRWLock('sharedResource',{connect});
|
|
const lock3 = new NamedPipeRWLock('sharedResource',{connect});
|
|
|
|
// 所有读锁应该能够同时获取
|
|
await Promise.all([
|
|
lock1.readLock(),
|
|
lock2.readLock(),
|
|
lock3.readLock()
|
|
]);
|
|
|
|
expect(lock1.isLocked).toBe(true);
|
|
expect(lock2.isLocked).toBe(true);
|
|
expect(lock3.isLocked).toBe(true);
|
|
|
|
// 释放所有锁
|
|
await Promise.all([
|
|
lock1.unlock(),
|
|
lock2.unlock(),
|
|
lock3.unlock()
|
|
]);
|
|
|
|
lock1.close();
|
|
lock2.close();
|
|
lock3.close();
|
|
});
|
|
|
|
test('should queue write lock when read locks exist', async () => {
|
|
const readLock1 = new NamedPipeRWLock('queuedResource',{connect});
|
|
const readLock2 = new NamedPipeRWLock('queuedResource',{connect});
|
|
const writeLock = new NamedPipeRWLock('queuedResource',{connect});
|
|
|
|
// 先获取两个读锁
|
|
await readLock1.readLock();
|
|
await readLock2.readLock();
|
|
|
|
// 尝试获取写锁,应该会被阻塞
|
|
let writeLockAcquired = false;
|
|
const writeLockPromise = writeLock.writeLock().then(() => {
|
|
writeLockAcquired = true;
|
|
});
|
|
|
|
// 等待一小段时间,写锁不应该被获取
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
expect(writeLockAcquired).toBe(false);
|
|
|
|
// 释放一个读锁
|
|
await readLock1.unlock();
|
|
|
|
// 等待一小段时间,写锁仍然不应该被获取(还有一个读锁)
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
expect(writeLockAcquired).toBe(false);
|
|
|
|
// 释放最后一个读锁
|
|
await readLock2.unlock();
|
|
|
|
// 现在写锁应该能被获取
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
expect(writeLockAcquired).toBe(true);
|
|
|
|
// 释放写锁
|
|
await writeLock.unlock();
|
|
|
|
readLock1.close();
|
|
readLock2.close();
|
|
writeLock.close();
|
|
});
|
|
|
|
test('should queue read locks when write lock exists', async () => {
|
|
const writeLock = new NamedPipeRWLock('queuedResource2',{connect});
|
|
const readLock1 = new NamedPipeRWLock('queuedResource2',{connect});
|
|
const readLock2 = new NamedPipeRWLock('queuedResource2',{connect});
|
|
|
|
// 先获取写锁
|
|
await writeLock.writeLock();
|
|
|
|
// 尝试获取读锁,应该会被阻塞
|
|
let readLockAcquired = false;
|
|
const readLockPromise = readLock1.readLock().then(() => {
|
|
readLockAcquired = true;
|
|
});
|
|
|
|
// 等待一小段时间,读锁不应该被获取
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
expect(readLockAcquired).toBe(false);
|
|
|
|
// 再尝试获取另一个读锁,也应该被阻塞
|
|
let readLock2Acquired = false;
|
|
const readLock2Promise = readLock2.readLock().then(() => {
|
|
readLock2Acquired = true;
|
|
});
|
|
|
|
// 等待一小段时间,第二个读锁也不应该被获取
|
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
expect(readLock2Acquired).toBe(false);
|
|
|
|
// 释放写锁
|
|
await writeLock.unlock();
|
|
|
|
// 现在读锁应该能被获取
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
expect(readLockAcquired).toBe(true);
|
|
expect(readLock2Acquired).toBe(true);
|
|
|
|
// 释放读锁
|
|
await Promise.all([
|
|
readLock1.unlock(),
|
|
readLock2.unlock()
|
|
]);
|
|
|
|
writeLock.close();
|
|
readLock1.close();
|
|
readLock2.close();
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
test('should reject when trying to acquire lock while already holding one', async () => {
|
|
const lock = new NamedPipeRWLock('errorResource',{connect});
|
|
|
|
await lock.readLock();
|
|
|
|
// 尝试在已经持有锁的情况下再获取锁
|
|
await expect(lock.writeLock()).rejects.toThrow('Lock already held');
|
|
|
|
await lock.unlock();
|
|
lock.close();
|
|
});
|
|
|
|
test('should handle lock acquisition timeout', async () => {
|
|
const lock = new NamedPipeRWLock('timeoutResource',{connect}, {
|
|
timeout: 100 // 设置很短的超时时间
|
|
});
|
|
|
|
// 模拟一个永远不会释放的锁场景
|
|
const blockingLock = new NamedPipeRWLock('timeoutResource',{connect});
|
|
await blockingLock.writeLock();
|
|
|
|
// 尝试获取已经被占用的锁,应该会超时
|
|
await expect(lock.writeLock()).rejects.toThrow(/timeout/);
|
|
|
|
await blockingLock.unlock();
|
|
blockingLock.close();
|
|
lock.close();
|
|
});
|
|
});
|
|
}); |