У меня есть сервер, который получает запросы одновременно, но некоторые ресурсы R/W не могут быть доступны одновременно, и мне нецелесообразно реализовывать блокировку на уровне базы данных, поэтому я хочу создать функцию, которая превращает параллельные запросы в последовательные. .
Идея состоит в том, чтобы иметь очереди с каждым отдельным идентификатором ресурса, и когда запрос получен, если очередь, соответствующая идентификатору ресурса, пуста, она выполняет нужную мне функцию, если нет, то просто ставит в очередь функцию, которая будет выполняться после другой одни закончились.
Я придумал какой-то код, который не работает, и я не понимаю, почему.
Вот пример скрипта :
export const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => { setTimeout(resolve, ms); });
type AsyncFn = () => Promise<void>;
const locks = new Map<string, AsyncFn[]>();
const unstackLock = async (id: string) => {
const stack = locks.get(id);
if (!stack) {
return;
}
const nextFn = stack.shift();
if (!nextFn) {
return;
}
try {
await nextFn();
} finally {
await unstackLock(id);
}
};
export const withLock = async (id: string, fn: AsyncFn): Promise<void> => {
if (!locks.has(id)) {
locks.set(id, []);
}
const lock = locks.get(id);
if (!lock) {
// never happens but makes TS happy
throw new Error('Lock is not defined');
}
lock.push(fn);
if (lock.length === 1) {
await unstackLock(id);
}
};
const test = async () => {
const results: number[] = [];
const f1 = withLock('lock', async () => {
await sleep(Math.random() * 100);
results.push(1);
});
const f2 = withLock('lock', async () => {
await sleep(Math.random() * 100);
results.push(2);
});
const f3 = withLock('lock', async () => {
await sleep(Math.random() * 100);
results.push(3);
});
await Promise.all([f1, f2, f3]);
if (results[0] !== 1 || results[1] !== 2 || results[2] !== 3) {
console.info('FAILED', results);
} else {
console.info('SUCCESS');
}
};
test();
Я хочу, чтобы массив results
был [1, 2, 3]
какой бы длины ни выполнялись f1
, f2
и f3
(которые, соответственно, подталкивают 1
, 2
и 3
к массиву results
), но в случайном порядке.
Массив results
не [1, 2, 3]
после каждого запуска
Ах я вижу. Мне просто посчастливилось получить SUCCESS
с первой попытки
Хорошо, я придумал версию, которая работает:
export const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => { setTimeout(resolve, ms); });
type AsyncFn = () => Promise<void>;
type Lock = {
running: Promise<void> | null;
queue: AsyncFn[];
}
const locks = new Map<string, Lock>();
const unstackLock = async (id: string) => {
const stack = locks.get(id);
if (!stack) {
return;
}
if (stack.queue.length === 0) {
return;
}
if (!stack.running) {
const fn = stack.queue.shift();
if (fn) {
stack.running = fn();
await stack.running;
stack.running = null;
await unstackLock(id);
}
}
};
export const withLock = async (id: string, fn: AsyncFn): Promise<void> => {
if (!locks.has(id)) {
locks.set(id, { running: null, queue: [] });
}
const lock = locks.get(id);
if (!lock) {
// never happens but makes TS happy
throw new Error('Lock is not defined');
}
lock.queue.push(fn);
return unstackLock(id);
};
const test = async () => {
const results: number[] = [];
const f1 = withLock('lock', async () => {
await sleep(Math.random() * 100);
results.push(1);
});
const f2 = withLock('lock', async () => {
await sleep(Math.random() * 100);
results.push(2);
});
const f3 = withLock('lock', async () => {
await sleep(Math.random() * 100);
results.push(3);
});
await Promise.all([f1, f2, f3]);
if (results[0] !== 1 || results[1] !== 2 || results[2] !== 3) {
console.info('FAILED', results);
} else {
console.info('SUCCESS');
}
};
test();
что не работает?