mirror of
https://github.com/denoland/deno.git
synced 2025-01-03 04:48:52 -05:00
fix(ops): Always close cancel handles for read_async/write_async (#17736)
Fixes https://github.com/denoland/deno/issues/17734
This commit is contained in:
parent
211f49619a
commit
0164959d34
4 changed files with 98 additions and 31 deletions
|
@ -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(
|
Deno.test(
|
||||||
{ permissions: { read: true }, ignore: Deno.build.os !== "linux" },
|
{ permissions: { read: true }, ignore: Deno.build.os !== "linux" },
|
||||||
async function readFileProcFs() {
|
async function readFileProcFs() {
|
||||||
|
|
|
@ -95,7 +95,7 @@ Deno.test(
|
||||||
queueMicrotask(() => ac.abort());
|
queueMicrotask(() => ac.abort());
|
||||||
const error = await assertRejects(
|
const error = await assertRejects(
|
||||||
async () => {
|
async () => {
|
||||||
await Deno.readFile("cli/tests/testdata/assets/fixture.json", {
|
await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", {
|
||||||
signal: ac.signal,
|
signal: ac.signal,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -113,7 +113,7 @@ Deno.test(
|
||||||
queueMicrotask(() => ac.abort(abortReason));
|
queueMicrotask(() => ac.abort(abortReason));
|
||||||
const error = await assertRejects(
|
const error = await assertRejects(
|
||||||
async () => {
|
async () => {
|
||||||
await Deno.readFile("cli/tests/testdata/assets/fixture.json", {
|
await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", {
|
||||||
signal: ac.signal,
|
signal: ac.signal,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -128,7 +128,7 @@ Deno.test(
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
queueMicrotask(() => ac.abort("Some string"));
|
queueMicrotask(() => ac.abort("Some string"));
|
||||||
try {
|
try {
|
||||||
await Deno.readFile("cli/tests/testdata/assets/fixture.json", {
|
await Deno.readTextFile("cli/tests/testdata/assets/fixture.json", {
|
||||||
signal: ac.signal,
|
signal: ac.signal,
|
||||||
});
|
});
|
||||||
unreachable();
|
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(
|
Deno.test(
|
||||||
{ permissions: { read: true }, ignore: Deno.build.os !== "linux" },
|
{ permissions: { read: true }, ignore: Deno.build.os !== "linux" },
|
||||||
async function readTextFileProcFs() {
|
async function readTextFileProcFs() {
|
||||||
|
|
|
@ -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) {
|
function assertNotExists(filename: string | URL) {
|
||||||
if (pathExists(filename)) {
|
if (pathExists(filename)) {
|
||||||
throw new Error(`The file ${filename} exists.`);
|
throw new Error(`The file ${filename} exists.`);
|
||||||
|
|
|
@ -267,14 +267,6 @@ async fn op_write_file_async(
|
||||||
data: ZeroCopyBuf,
|
data: ZeroCopyBuf,
|
||||||
cancel_rid: Option<ResourceId>,
|
cancel_rid: Option<ResourceId>,
|
||||||
) -> Result<(), AnyError> {
|
) -> 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(
|
let (path, open_options) = open_helper(
|
||||||
&mut state.borrow_mut(),
|
&mut state.borrow_mut(),
|
||||||
&path,
|
&path,
|
||||||
|
@ -282,15 +274,30 @@ async fn op_write_file_async(
|
||||||
Some(&write_open_options(create, append, create_new)),
|
Some(&write_open_options(create, append, create_new)),
|
||||||
"Deno.writeFile()",
|
"Deno.writeFile()",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let write_future = tokio::task::spawn_blocking(move || {
|
let write_future = tokio::task::spawn_blocking(move || {
|
||||||
write_file(&path, open_options, mode, data)
|
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 {
|
if let Some(cancel_handle) = cancel_handle {
|
||||||
write_future.or_cancel(cancel_handle).await???;
|
let write_future_rv = write_future.or_cancel(cancel_handle).await;
|
||||||
} else {
|
|
||||||
write_future.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(
|
fn write_file(
|
||||||
|
@ -2046,20 +2053,31 @@ async fn op_readfile_async(
|
||||||
.borrow_mut::<PermissionsContainer>()
|
.borrow_mut::<PermissionsContainer>()
|
||||||
.check_read(path, "Deno.readFile()")?;
|
.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);
|
let path = Path::new(&path);
|
||||||
Ok(std::fs::read(path).map(ZeroCopyBuf::from)?)
|
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()
|
.borrow_mut()
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<CancelHandle>(cancel_rid);
|
.get::<CancelHandle>(rid)
|
||||||
if let Ok(cancel_handle) = cancel_handle {
|
.ok()
|
||||||
return fut.or_cancel(cancel_handle).await??;
|
});
|
||||||
}
|
|
||||||
|
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]
|
#[op]
|
||||||
|
@ -2075,20 +2093,31 @@ async fn op_readfile_text_async(
|
||||||
.borrow_mut::<PermissionsContainer>()
|
.borrow_mut::<PermissionsContainer>()
|
||||||
.check_read(path, "Deno.readTextFile()")?;
|
.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);
|
let path = Path::new(&path);
|
||||||
Ok(string_from_utf8_lossy(std::fs::read(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()
|
.borrow_mut()
|
||||||
.resource_table
|
.resource_table
|
||||||
.get::<CancelHandle>(cancel_rid);
|
.get::<CancelHandle>(rid)
|
||||||
if let Ok(cancel_handle) = cancel_handle {
|
.ok()
|
||||||
return fut.or_cancel(cancel_handle).await??;
|
});
|
||||||
}
|
|
||||||
|
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
|
// Like String::from_utf8_lossy but operates on owned values
|
||||||
|
|
Loading…
Reference in a new issue