From 8b8b21b553fba03124934363e77e636adaeb4745 Mon Sep 17 00:00:00 2001 From: Aaron O'Mullan Date: Wed, 27 Apr 2022 07:03:44 -0700 Subject: [PATCH] perf(runtime): read entire files in single ops (#14261) Co-authored-by: Divy Srivastava --- cli/tests/unit/read_file_test.ts | 40 ---------------- runtime/js/40_read_file.js | 73 +++++++++++++++++++---------- runtime/ops/fs.rs | 80 ++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 64 deletions(-) diff --git a/cli/tests/unit/read_file_test.ts b/cli/tests/unit/read_file_test.ts index 1f3eea30a3..07935b7fbb 100644 --- a/cli/tests/unit/read_file_test.ts +++ b/cli/tests/unit/read_file_test.ts @@ -1,5 +1,4 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import { writeAllSync } from "../../../test_util/std/io/util.ts"; import { assert, assertEquals, @@ -152,42 +151,3 @@ Deno.test( assert(data.byteLength > 0); }, ); - -Deno.test( - { permissions: { read: true, write: true } }, - async function readFileExtendedDuringRead() { - // Write 128MB file - const filename = Deno.makeTempDirSync() + "/test.txt"; - const data = new Uint8Array(1024 * 1024 * 128); - Deno.writeFileSync(filename, data); - const promise = Deno.readFile(filename); - queueMicrotask(() => { - // Append 128MB to file - const f = Deno.openSync(filename, { append: true }); - writeAllSync(f, data); - f.close(); - }); - const read = await promise; - assertEquals(read.byteLength, data.byteLength * 2); - }, -); - -Deno.test( - { permissions: { read: true, write: true } }, - async function readFile0LengthExtendedDuringRead() { - // Write 0 byte file - const filename = Deno.makeTempDirSync() + "/test.txt"; - const first = new Uint8Array(0); - const second = new Uint8Array(1024 * 1024 * 128); - Deno.writeFileSync(filename, first); - const promise = Deno.readFile(filename); - queueMicrotask(() => { - // Append 128MB to file - const f = Deno.openSync(filename, { append: true }); - writeAllSync(f, second); - f.close(); - }); - const read = await promise; - assertEquals(read.byteLength, second.byteLength); - }, -); diff --git a/runtime/js/40_read_file.js b/runtime/js/40_read_file.js index 5862454db8..8a52e4f700 100644 --- a/runtime/js/40_read_file.js +++ b/runtime/js/40_read_file.js @@ -3,44 +3,69 @@ ((window) => { const core = window.Deno.core; - const { open, openSync } = window.__bootstrap.files; - const { readAllSync, readAll, readAllSyncSized, readAllInnerSized } = - window.__bootstrap.io; + const { pathFromURL } = window.__bootstrap.util; + const { abortSignal } = window.__bootstrap; function readFileSync(path) { - const file = openSync(path); - try { - const { size } = file.statSync(); - if (size === 0) { - return readAllSync(file); - } else { - return readAllSyncSized(file, size); - } - } finally { - file.close(); - } + return core.opSync("op_readfile_sync", pathFromURL(path)); } async function readFile(path, options) { - const file = await open(path); + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = core.opSync("op_cancel_handle"); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); + } + try { - const { size } = await file.stat(); - if (size === 0) { - return await readAll(file); - } else { - return await readAllInnerSized(file, size, options); - } + const read = await core.opAsync( + "op_readfile_async", + pathFromURL(path), + cancelRid, + ); + return read; } finally { - file.close(); + if (options?.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted + options.signal.throwIfAborted(); + } } } function readTextFileSync(path) { - return core.decode(readFileSync(path)); + return core.opSync("op_readfile_text_sync", pathFromURL(path)); } async function readTextFile(path, options) { - return core.decode(await readFile(path, options)); + let cancelRid; + let abortHandler; + if (options?.signal) { + options.signal.throwIfAborted(); + cancelRid = core.opSync("op_cancel_handle"); + abortHandler = () => core.tryClose(cancelRid); + options.signal[abortSignal.add](abortHandler); + } + + try { + const read = await core.opAsync( + "op_readfile_text_async", + pathFromURL(path), + cancelRid, + ); + return read; + } finally { + if (options?.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted + options.signal.throwIfAborted(); + } + } } window.__bootstrap.readFile = { diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs index ddd7f9ca23..501c2ac5fd 100644 --- a/runtime/ops/fs.rs +++ b/runtime/ops/fs.rs @@ -95,6 +95,10 @@ pub fn init() -> Extension { op_futime_async::decl(), op_utime_sync::decl(), op_utime_async::decl(), + op_readfile_sync::decl(), + op_readfile_text_sync::decl(), + op_readfile_async::decl(), + op_readfile_text_async::decl(), ]) .build() } @@ -2008,3 +2012,79 @@ fn op_cwd(state: &mut OpState) -> Result { let path_str = into_string(path.into_os_string())?; Ok(path_str) } + +#[op] +fn op_readfile_sync( + state: &mut OpState, + path: String, +) -> Result { + let permissions = state.borrow_mut::(); + let path = Path::new(&path); + permissions.read.check(path)?; + Ok(std::fs::read(path)?.into()) +} + +#[op] +fn op_readfile_text_sync( + state: &mut OpState, + path: String, +) -> Result { + let permissions = state.borrow_mut::(); + let path = Path::new(&path); + permissions.read.check(path)?; + Ok(std::fs::read_to_string(path)?) +} + +#[op] +async fn op_readfile_async( + state: Rc>, + path: String, + cancel_rid: Option, +) -> Result { + { + let path = Path::new(&path); + let mut state = state.borrow_mut(); + state.borrow_mut::().read.check(path)?; + } + let fut = 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 + .borrow_mut() + .resource_table + .get::(cancel_rid); + if let Ok(cancel_handle) = cancel_handle { + return fut.or_cancel(cancel_handle).await??; + } + } + fut.await? +} + +#[op] +async fn op_readfile_text_async( + state: Rc>, + path: String, + cancel_rid: Option, +) -> Result { + { + let path = Path::new(&path); + let mut state = state.borrow_mut(); + state.borrow_mut::().read.check(path)?; + } + let fut = tokio::task::spawn_blocking(move || { + let path = Path::new(&path); + Ok(String::from_utf8(std::fs::read(path)?)?) + }); + if let Some(cancel_rid) = cancel_rid { + let cancel_handle = state + .borrow_mut() + .resource_table + .get::(cancel_rid); + if let Ok(cancel_handle) = cancel_handle { + return fut.or_cancel(cancel_handle).await??; + } + } + fut.await? +}