diff options
author | Kamil Ogórek <kamil.ogorek@gmail.com> | 2023-02-11 13:19:13 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-11 14:19:13 +0200 |
commit | 0164959d3441ccfbaaaff20eea2d3ec9fe852373 (patch) | |
tree | 833373ac2ef41225a24a48346afc12deccfc78d9 | |
parent | 211f49619afd915a2ce56cf0b5da50f640c90f73 (diff) |
fix(ops): Always close cancel handles for read_async/write_async (#17736)
Fixes https://github.com/denoland/deno/issues/17734
-rw-r--r-- | cli/tests/unit/read_file_test.ts | 11 | ||||
-rw-r--r-- | cli/tests/unit/read_text_file_test.ts | 17 | ||||
-rw-r--r-- | cli/tests/unit/write_file_test.ts | 16 | ||||
-rw-r--r-- | runtime/ops/fs.rs | 85 |
4 files changed, 98 insertions, 31 deletions
diff --git a/cli/tests/unit/read_file_test.ts b/cli/tests/unit/read_file_test.ts index 0d25ea6d8..24cf6dccf 100644 --- a/cli/tests/unit/read_file_test.ts +++ b/cli/tests/unit/read_file_test.ts @@ -140,6 +140,17 @@ Deno.test( }, ); +// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. +Deno.test( + { permissions: { read: true } }, + async function readFileWithAbortSignalNotCalled() { + const ac = new AbortController(); + await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + signal: ac.signal, + }); + }, +); + Deno.test( { permissions: { read: true }, ignore: Deno.build.os !== "linux" }, async function readFileProcFs() { diff --git a/cli/tests/unit/read_text_file_test.ts b/cli/tests/unit/read_text_file_test.ts index e78276dde..c40cb83e3 100644 --- a/cli/tests/unit/read_text_file_test.ts +++ b/cli/tests/unit/read_text_file_test.ts @@ -95,7 +95,7 @@ Deno.test( queueMicrotask(() => ac.abort()); const error = await assertRejects( async () => { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { signal: ac.signal, }); }, @@ -113,7 +113,7 @@ Deno.test( queueMicrotask(() => ac.abort(abortReason)); const error = await assertRejects( async () => { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { signal: ac.signal, }); }, @@ -128,7 +128,7 @@ Deno.test( const ac = new AbortController(); queueMicrotask(() => ac.abort("Some string")); try { - await Deno.readFile("cli/tests/testdata/assets/fixture.json", { + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { signal: ac.signal, }); unreachable(); @@ -138,6 +138,17 @@ Deno.test( }, ); +// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. +Deno.test( + { permissions: { read: true } }, + async function readTextFileWithAbortSignalNotCalled() { + const ac = new AbortController(); + await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", { + signal: ac.signal, + }); + }, +); + Deno.test( { permissions: { read: true }, ignore: Deno.build.os !== "linux" }, async function readTextFileProcFs() { diff --git a/cli/tests/unit/write_file_test.ts b/cli/tests/unit/write_file_test.ts index 78d0f5bad..5f1ffd7a6 100644 --- a/cli/tests/unit/write_file_test.ts +++ b/cli/tests/unit/write_file_test.ts @@ -379,6 +379,22 @@ Deno.test( }, ); +// Test that AbortController's cancel handle is cleaned-up correctly, and do not leak resources. +Deno.test( + { permissions: { read: true, write: true } }, + async function writeFileWithAbortSignalNotCalled() { + const ac = new AbortController(); + const enc = new TextEncoder(); + const data = enc.encode("Hello"); + const filename = Deno.makeTempDirSync() + "/test.txt"; + await Deno.writeFile(filename, data, { signal: ac.signal }); + const dataRead = Deno.readFileSync(filename); + const dec = new TextDecoder("utf-8"); + const actual = dec.decode(dataRead); + assertEquals(actual, "Hello"); + }, +); + function assertNotExists(filename: string | URL) { if (pathExists(filename)) { throw new Error(`The file ${filename} exists.`); diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs index f6b9a58eb..3cac56443 100644 --- a/runtime/ops/fs.rs +++ b/runtime/ops/fs.rs @@ -267,14 +267,6 @@ async fn op_write_file_async( data: ZeroCopyBuf, cancel_rid: Option<ResourceId>, ) -> Result<(), AnyError> { - let cancel_handle = match cancel_rid { - Some(cancel_rid) => state - .borrow_mut() - .resource_table - .get::<CancelHandle>(cancel_rid) - .ok(), - None => None, - }; let (path, open_options) = open_helper( &mut state.borrow_mut(), &path, @@ -282,15 +274,30 @@ async fn op_write_file_async( Some(&write_open_options(create, append, create_new)), "Deno.writeFile()", )?; + let write_future = tokio::task::spawn_blocking(move || { write_file(&path, open_options, mode, data) }); + + let cancel_handle = cancel_rid.and_then(|rid| { + state + .borrow_mut() + .resource_table + .get::<CancelHandle>(rid) + .ok() + }); + if let Some(cancel_handle) = cancel_handle { - write_future.or_cancel(cancel_handle).await???; - } else { - write_future.await??; + let write_future_rv = write_future.or_cancel(cancel_handle).await; + + if let Some(cancel_rid) = cancel_rid { + state.borrow_mut().resource_table.close(cancel_rid).ok(); + }; + + return write_future_rv??; } - Ok(()) + + write_future.await? } fn write_file( @@ -2046,20 +2053,31 @@ async fn op_readfile_async( .borrow_mut::<PermissionsContainer>() .check_read(path, "Deno.readFile()")?; } - let fut = tokio::task::spawn_blocking(move || { + + let read_future = tokio::task::spawn_blocking(move || { let path = Path::new(&path); Ok(std::fs::read(path).map(ZeroCopyBuf::from)?) }); - if let Some(cancel_rid) = cancel_rid { - let cancel_handle = state + + let cancel_handle = cancel_rid.and_then(|rid| { + state .borrow_mut() .resource_table - .get::<CancelHandle>(cancel_rid); - if let Ok(cancel_handle) = cancel_handle { - return fut.or_cancel(cancel_handle).await??; - } + .get::<CancelHandle>(rid) + .ok() + }); + + if let Some(cancel_handle) = cancel_handle { + let read_future_rv = read_future.or_cancel(cancel_handle).await; + + if let Some(cancel_rid) = cancel_rid { + state.borrow_mut().resource_table.close(cancel_rid).ok(); + }; + + return read_future_rv??; } - fut.await? + + read_future.await? } #[op] @@ -2075,20 +2093,31 @@ async fn op_readfile_text_async( .borrow_mut::<PermissionsContainer>() .check_read(path, "Deno.readTextFile()")?; } - let fut = tokio::task::spawn_blocking(move || { + + let read_future = tokio::task::spawn_blocking(move || { let path = Path::new(&path); Ok(string_from_utf8_lossy(std::fs::read(path)?)) }); - if let Some(cancel_rid) = cancel_rid { - let cancel_handle = state + + let cancel_handle = cancel_rid.and_then(|rid| { + state .borrow_mut() .resource_table - .get::<CancelHandle>(cancel_rid); - if let Ok(cancel_handle) = cancel_handle { - return fut.or_cancel(cancel_handle).await??; - } + .get::<CancelHandle>(rid) + .ok() + }); + + if let Some(cancel_handle) = cancel_handle { + let read_future_rv = read_future.or_cancel(cancel_handle).await; + + if let Some(cancel_rid) = cancel_rid { + state.borrow_mut().resource_table.close(cancel_rid).ok(); + }; + + return read_future_rv??; } - fut.await? + + read_future.await? } // Like String::from_utf8_lossy but operates on owned values |