mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 20:38:55 +00:00
feat(unstable): Deno.FsFile.lock[Sync]()
and Deno.FsFile.unlock[Sync]()
(#22235)
Closes #22178.
This commit is contained in:
parent
07a94984e1
commit
0f7f987951
@ -898,3 +898,198 @@ Deno.test(
|
||||
await Deno.remove(filename);
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
{ permissions: { read: true, run: true, hrtime: true } },
|
||||
async function fsFileLockFileSync() {
|
||||
await runFlockTests({ sync: true });
|
||||
},
|
||||
);
|
||||
|
||||
Deno.test(
|
||||
{ permissions: { read: true, run: true, hrtime: true } },
|
||||
async function fsFileLockFileAsync() {
|
||||
await runFlockTests({ sync: false });
|
||||
},
|
||||
);
|
||||
|
||||
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,
|
||||
// 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,
|
||||
}),
|
||||
false,
|
||||
"shared does not block shared",
|
||||
);
|
||||
}
|
||||
|
||||
async function checkFirstBlocksSecond(opts: {
|
||||
firstExclusive: boolean;
|
||||
secondExclusive: boolean;
|
||||
sync: boolean;
|
||||
waitBothEnteredLock?: boolean;
|
||||
}) {
|
||||
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));
|
||||
|
||||
await Promise.all([
|
||||
firstProcess.waitStartup(),
|
||||
secondProcess.waitStartup(),
|
||||
]);
|
||||
|
||||
await firstProcess.enterLock();
|
||||
await firstProcess.waitEnterLock();
|
||||
|
||||
await secondProcess.enterLock();
|
||||
await sleep(100);
|
||||
|
||||
if (!opts.waitBothEnteredLock) {
|
||||
await firstProcess.exitLock();
|
||||
}
|
||||
|
||||
await secondProcess.waitEnterLock();
|
||||
|
||||
if (opts.waitBothEnteredLock) {
|
||||
await firstProcess.exitLock();
|
||||
}
|
||||
|
||||
await secondProcess.exitLock();
|
||||
|
||||
// collect the final output
|
||||
const firstPsTimes = await firstProcess.getTimes();
|
||||
const secondPsTimes = await secondProcess.getTimes();
|
||||
return firstPsTimes.exitTime < secondPsTimes.enterTime;
|
||||
} finally {
|
||||
await firstProcess.close();
|
||||
await secondProcess.close();
|
||||
}
|
||||
}
|
||||
|
||||
function runFlockTestProcess(opts: { exclusive: boolean; sync: boolean }) {
|
||||
const path = "cli/tests/testdata/assets/lock_target.txt";
|
||||
const scriptText = `
|
||||
const file = 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
|
||||
? `file.lockSync(${opts.exclusive ? "true" : "false"});`
|
||||
: `await file.lock(${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
|
||||
// occur at the same time as this exit time
|
||||
const exitTime = new Date().getTime();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// release the lock
|
||||
${opts.sync ? "file.unlockSync();" : "await file.unlock();"}
|
||||
|
||||
// exited signal
|
||||
Deno.stdout.writeSync(new Uint8Array(1));
|
||||
|
||||
// output the enter and exit time
|
||||
console.log(JSON.stringify({ enterTime, exitTime }));
|
||||
`;
|
||||
|
||||
const process = new Deno.Command(Deno.execPath(), {
|
||||
args: ["eval", "--unstable", scriptText],
|
||||
stdin: "piped",
|
||||
stdout: "piped",
|
||||
stderr: "null",
|
||||
}).spawn();
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
return {
|
||||
async waitStartup() {
|
||||
await waitSignal();
|
||||
},
|
||||
async enterLock() {
|
||||
await signal();
|
||||
await waitSignal(); // entering signal
|
||||
},
|
||||
async waitEnterLock() {
|
||||
await waitSignal();
|
||||
},
|
||||
async exitLock() {
|
||||
await signal();
|
||||
await waitSignal();
|
||||
},
|
||||
getTimes: async () => {
|
||||
const { stdout } = await process.output();
|
||||
const text = new TextDecoder().decode(stdout);
|
||||
return JSON.parse(text) as {
|
||||
enterTime: number;
|
||||
exitTime: number;
|
||||
};
|
||||
},
|
||||
close: async () => {
|
||||
await process.status;
|
||||
await process.stdin.close();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
24
cli/tsc/dts/lib.deno.ns.d.ts
vendored
24
cli/tsc/dts/lib.deno.ns.d.ts
vendored
@ -2666,6 +2666,30 @@ declare namespace Deno {
|
||||
* @category File System
|
||||
*/
|
||||
utimeSync(atime: number | Date, mtime: number | Date): void;
|
||||
/** **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* Acquire an advisory file-system lock for the file.
|
||||
*
|
||||
* @param [exclusive=false]
|
||||
*/
|
||||
lock(exclusive?: boolean): Promise<void>;
|
||||
/** **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* Synchronously acquire an advisory file-system lock synchronously for the file.
|
||||
*
|
||||
* @param [exclusive=false]
|
||||
*/
|
||||
lockSync(exclusive?: boolean): void;
|
||||
/** **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* Release an advisory file-system lock for the file.
|
||||
*/
|
||||
unlock(): Promise<void>;
|
||||
/** **UNSTABLE**: New API, yet to be vetted.
|
||||
*
|
||||
* Synchronously release an advisory file-system lock for the file.
|
||||
*/
|
||||
unlockSync(): void;
|
||||
/** Close the file. Closing a file when you are finished with it is
|
||||
* important to avoid leaking resources.
|
||||
*
|
||||
|
@ -766,6 +766,22 @@ class FsFile {
|
||||
futimeSync(this.#rid, atime, mtime);
|
||||
}
|
||||
|
||||
lockSync(exclusive = false) {
|
||||
op_fs_flock_sync(this.#rid, exclusive);
|
||||
}
|
||||
|
||||
async lock(exclusive = false) {
|
||||
await op_fs_flock_async(this.#rid, exclusive);
|
||||
}
|
||||
|
||||
unlockSync() {
|
||||
op_fs_funlock_sync(this.#rid);
|
||||
}
|
||||
|
||||
async unlock() {
|
||||
await op_fs_funlock_async(this.#rid);
|
||||
}
|
||||
|
||||
[SymbolDispose]() {
|
||||
core.tryClose(this.#rid);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user