diff options
author | Asher Gomez <ashersaupingomez@gmail.com> | 2024-02-05 10:11:54 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-05 00:11:54 +0100 |
commit | 0f7f98795129f3f6bef51d08da9accca5e9ca9cc (patch) | |
tree | cb71c1358b9235a7f5098623750712e3de693277 /cli/tests/unit/files_test.ts | |
parent | 07a94984e1e3ca24dcaf114ac5ff82c8c3510894 (diff) |
feat(unstable): `Deno.FsFile.lock[Sync]()` and `Deno.FsFile.unlock[Sync]()` (#22235)
Closes #22178.
Diffstat (limited to 'cli/tests/unit/files_test.ts')
-rw-r--r-- | cli/tests/unit/files_test.ts | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/cli/tests/unit/files_test.ts b/cli/tests/unit/files_test.ts index a96f60a7a..aed09e814 100644 --- a/cli/tests/unit/files_test.ts +++ b/cli/tests/unit/files_test.ts @@ -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(); + }, + }; +} |