2023-01-02 16:00:42 -05:00
|
|
|
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
2021-11-23 11:45:18 -05:00
|
|
|
import { assertEquals } from "./test_util.ts";
|
2021-08-24 09:21:31 -04:00
|
|
|
|
2021-11-23 11:45:18 -05:00
|
|
|
Deno.test(
|
2021-09-22 19:50:50 -04:00
|
|
|
{ permissions: { read: true, run: true, hrtime: true } },
|
2021-08-24 09:21:31 -04:00
|
|
|
async function flockFileSync() {
|
2021-09-17 09:02:23 -04:00
|
|
|
await runFlockTests({ sync: true });
|
2021-08-24 09:21:31 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2021-11-23 11:45:18 -05:00
|
|
|
Deno.test(
|
2021-09-22 19:50:50 -04:00
|
|
|
{ permissions: { read: true, run: true, hrtime: true } },
|
2021-08-24 09:21:31 -04:00
|
|
|
async function flockFileAsync() {
|
2021-09-17 09:02:23 -04:00
|
|
|
await runFlockTests({ sync: false });
|
2021-08-24 09:21:31 -04:00
|
|
|
},
|
|
|
|
);
|
2021-09-17 09:02:23 -04:00
|
|
|
|
|
|
|
async function runFlockTests(opts: { sync: boolean }) {
|
|
|
|
assertEquals(
|
|
|
|
await checkFirstBlocksSecond({
|
|
|
|
firstExclusive: true,
|
|
|
|
secondExclusive: false,
|
|
|
|
sync: opts.sync,
|
|
|
|
}),
|
|
|
|
true,
|
|
|
|
"exclusive blocks shared",
|
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
await checkFirstBlocksSecond({
|
|
|
|
firstExclusive: false,
|
|
|
|
secondExclusive: true,
|
|
|
|
sync: opts.sync,
|
|
|
|
}),
|
|
|
|
true,
|
|
|
|
"shared blocks exclusive",
|
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
await checkFirstBlocksSecond({
|
|
|
|
firstExclusive: true,
|
|
|
|
secondExclusive: true,
|
|
|
|
sync: opts.sync,
|
|
|
|
}),
|
|
|
|
true,
|
|
|
|
"exclusive blocks exclusive",
|
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
await checkFirstBlocksSecond({
|
|
|
|
firstExclusive: false,
|
|
|
|
secondExclusive: false,
|
|
|
|
sync: opts.sync,
|
2021-09-30 15:45:13 -04:00
|
|
|
// need to wait for both to enter the lock to prevent the case where the
|
|
|
|
// first process enters and exits the lock before the second even enters
|
|
|
|
waitBothEnteredLock: true,
|
2021-09-17 09:02:23 -04:00
|
|
|
}),
|
|
|
|
false,
|
|
|
|
"shared does not block shared",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function checkFirstBlocksSecond(opts: {
|
|
|
|
firstExclusive: boolean;
|
|
|
|
secondExclusive: boolean;
|
|
|
|
sync: boolean;
|
2021-09-30 15:45:13 -04:00
|
|
|
waitBothEnteredLock?: boolean;
|
2021-09-17 09:02:23 -04:00
|
|
|
}) {
|
|
|
|
const firstProcess = runFlockTestProcess({
|
|
|
|
exclusive: opts.firstExclusive,
|
|
|
|
sync: opts.sync,
|
|
|
|
});
|
|
|
|
const secondProcess = runFlockTestProcess({
|
|
|
|
exclusive: opts.secondExclusive,
|
|
|
|
sync: opts.sync,
|
|
|
|
});
|
|
|
|
try {
|
|
|
|
const sleep = (time: number) => new Promise((r) => setTimeout(r, time));
|
|
|
|
|
2021-09-30 15:45:13 -04:00
|
|
|
await Promise.all([
|
|
|
|
firstProcess.waitStartup(),
|
|
|
|
secondProcess.waitStartup(),
|
|
|
|
]);
|
2021-09-17 09:02:23 -04:00
|
|
|
|
2021-09-30 15:45:13 -04:00
|
|
|
await firstProcess.enterLock();
|
|
|
|
await firstProcess.waitEnterLock();
|
|
|
|
|
|
|
|
await secondProcess.enterLock();
|
2021-09-17 14:17:01 -04:00
|
|
|
await sleep(100);
|
2021-09-30 15:45:13 -04:00
|
|
|
|
|
|
|
if (!opts.waitBothEnteredLock) {
|
|
|
|
await firstProcess.exitLock();
|
|
|
|
}
|
|
|
|
|
|
|
|
await secondProcess.waitEnterLock();
|
|
|
|
|
|
|
|
if (opts.waitBothEnteredLock) {
|
|
|
|
await firstProcess.exitLock();
|
|
|
|
}
|
|
|
|
|
|
|
|
await secondProcess.exitLock();
|
|
|
|
|
|
|
|
// collect the final output
|
2021-09-17 14:17:01 -04:00
|
|
|
const firstPsTimes = await firstProcess.getTimes();
|
2021-09-17 09:02:23 -04:00
|
|
|
const secondPsTimes = await secondProcess.getTimes();
|
|
|
|
return firstPsTimes.exitTime < secondPsTimes.enterTime;
|
|
|
|
} finally {
|
2022-05-18 16:00:11 -04:00
|
|
|
await firstProcess.close();
|
|
|
|
await secondProcess.close();
|
2021-09-17 09:02:23 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function runFlockTestProcess(opts: { exclusive: boolean; sync: boolean }) {
|
2022-09-19 10:32:21 -04:00
|
|
|
const path = "cli/tests/testdata/assets/fixture.json";
|
2021-09-17 09:02:23 -04:00
|
|
|
const scriptText = `
|
|
|
|
const { rid } = Deno.openSync("${path}");
|
|
|
|
|
|
|
|
// ready signal
|
|
|
|
Deno.stdout.writeSync(new Uint8Array(1));
|
|
|
|
// wait for enter lock signal
|
|
|
|
Deno.stdin.readSync(new Uint8Array(1));
|
|
|
|
|
|
|
|
// entering signal
|
|
|
|
Deno.stdout.writeSync(new Uint8Array(1));
|
|
|
|
// lock and record the entry time
|
|
|
|
${
|
|
|
|
opts.sync
|
|
|
|
? `Deno.flockSync(rid, ${opts.exclusive ? "true" : "false"});`
|
|
|
|
: `await Deno.flock(rid, ${opts.exclusive ? "true" : "false"});`
|
|
|
|
}
|
|
|
|
const enterTime = new Date().getTime();
|
|
|
|
// entered signal
|
|
|
|
Deno.stdout.writeSync(new Uint8Array(1));
|
|
|
|
|
|
|
|
// wait for exit lock signal
|
|
|
|
Deno.stdin.readSync(new Uint8Array(1));
|
|
|
|
|
|
|
|
// record the exit time and wait a little bit before releasing
|
|
|
|
// the lock so that the enter time of the next process doesn't
|
2021-09-17 14:17:01 -04:00
|
|
|
// occur at the same time as this exit time
|
2021-09-17 09:02:23 -04:00
|
|
|
const exitTime = new Date().getTime();
|
2022-06-13 15:28:00 -04:00
|
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
2021-09-17 09:02:23 -04:00
|
|
|
|
|
|
|
// release the lock
|
|
|
|
${opts.sync ? "Deno.funlockSync(rid);" : "await Deno.funlock(rid);"}
|
|
|
|
|
2021-09-30 15:45:13 -04:00
|
|
|
// exited signal
|
|
|
|
Deno.stdout.writeSync(new Uint8Array(1));
|
|
|
|
|
2021-09-17 09:02:23 -04:00
|
|
|
// output the enter and exit time
|
|
|
|
console.log(JSON.stringify({ enterTime, exitTime }));
|
|
|
|
`;
|
|
|
|
|
2022-12-02 08:43:17 -05:00
|
|
|
const process = new Deno.Command(Deno.execPath(), {
|
2022-05-18 16:00:11 -04:00
|
|
|
args: ["eval", "--unstable", scriptText],
|
2021-09-17 09:02:23 -04:00
|
|
|
stdin: "piped",
|
2022-12-12 23:12:19 -05:00
|
|
|
stdout: "piped",
|
|
|
|
stderr: "null",
|
2022-12-02 08:43:17 -05:00
|
|
|
}).spawn();
|
2021-09-17 09:02:23 -04:00
|
|
|
|
2022-05-18 16:00:11 -04:00
|
|
|
const waitSignal = async () => {
|
|
|
|
const reader = process.stdout.getReader({ mode: "byob" });
|
|
|
|
await reader.read(new Uint8Array(1));
|
|
|
|
reader.releaseLock();
|
|
|
|
};
|
|
|
|
const signal = async () => {
|
|
|
|
const writer = process.stdin.getWriter();
|
|
|
|
await writer.write(new Uint8Array(1));
|
|
|
|
writer.releaseLock();
|
|
|
|
};
|
2021-09-30 15:45:13 -04:00
|
|
|
|
2021-09-17 09:02:23 -04:00
|
|
|
return {
|
2021-09-30 15:45:13 -04:00
|
|
|
async waitStartup() {
|
|
|
|
await waitSignal();
|
|
|
|
},
|
|
|
|
async enterLock() {
|
|
|
|
await signal();
|
|
|
|
await waitSignal(); // entering signal
|
|
|
|
},
|
|
|
|
async waitEnterLock() {
|
|
|
|
await waitSignal();
|
|
|
|
},
|
|
|
|
async exitLock() {
|
|
|
|
await signal();
|
|
|
|
await waitSignal();
|
|
|
|
},
|
2021-09-17 09:02:23 -04:00
|
|
|
getTimes: async () => {
|
2022-05-18 16:00:11 -04:00
|
|
|
const { stdout } = await process.output();
|
|
|
|
const text = new TextDecoder().decode(stdout);
|
2021-09-17 09:02:23 -04:00
|
|
|
return JSON.parse(text) as {
|
|
|
|
enterTime: number;
|
|
|
|
exitTime: number;
|
|
|
|
};
|
|
|
|
},
|
2022-05-18 16:00:11 -04:00
|
|
|
close: async () => {
|
|
|
|
await process.status;
|
|
|
|
await process.stdin.close();
|
2021-09-17 09:02:23 -04:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|