diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2022-04-18 18:00:14 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-18 18:00:14 -0400 |
commit | a64e63c3614b98aa2b51fb6b7ef4e30251e03111 (patch) | |
tree | be380bdc2d7c61126c594708aa97cacede9b6316 /runtime | |
parent | ca3b20df3c270f1bf5f1a7980c083c22a590420d (diff) |
perf: move Deno.writeTextFile and like functions to Rust (#14221)
Co-authored-by: Luca Casonato <hello@lcas.dev>
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/js/40_write_file.js | 93 | ||||
-rw-r--r-- | runtime/ops/fs.rs | 168 |
2 files changed, 159 insertions, 102 deletions
diff --git a/runtime/js/40_write_file.js b/runtime/js/40_write_file.js index 8eac953d4..462d71266 100644 --- a/runtime/js/40_write_file.js +++ b/runtime/js/40_write_file.js @@ -1,12 +1,9 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. "use strict"; ((window) => { - const { stat, statSync, chmod, chmodSync } = window.__bootstrap.fs; - const { open, openSync } = window.__bootstrap.files; - const { build } = window.__bootstrap.build; - const { - TypedArrayPrototypeSubarray, - } = window.__bootstrap.primordials; + const core = window.__bootstrap.core; + const { abortSignal } = window.__bootstrap; + const { pathFromURL } = window.__bootstrap.util; function writeFileSync( path, @@ -14,33 +11,13 @@ options = {}, ) { options.signal?.throwIfAborted(); - if (options.create !== undefined) { - const create = !!options.create; - if (!create) { - // verify that file exists - statSync(path); - } - } - - const openOptions = options.append - ? { write: true, create: true, append: true } - : { write: true, create: true, truncate: true }; - const file = openSync(path, openOptions); - - if ( - options.mode !== undefined && - options.mode !== null && - build.os !== "windows" - ) { - chmodSync(path, options.mode); - } - - let nwritten = 0; - while (nwritten < data.length) { - nwritten += file.writeSync(TypedArrayPrototypeSubarray(data, nwritten)); - } - - file.close(); + core.opSync("op_write_file_sync", { + path: pathFromURL(path), + data, + mode: options.mode, + append: options.append ?? false, + create: options.create ?? true, + }); } async function writeFile( @@ -48,38 +25,30 @@ data, options = {}, ) { - if (options.create !== undefined) { - const create = !!options.create; - if (!create) { - // verify that file exists - await stat(path); - } - } - - const openOptions = options.append - ? { write: true, create: true, append: true } - : { write: true, create: true, truncate: true }; - const file = await open(path, openOptions); - - if ( - options.mode !== undefined && - options.mode !== null && - build.os !== "windows" - ) { - await chmod(path, options.mode); + 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); } - - const signal = options?.signal ?? null; - let nwritten = 0; try { - while (nwritten < data.length) { - signal?.throwIfAborted(); - nwritten += await file.write( - TypedArrayPrototypeSubarray(data, nwritten), - ); - } + await core.opAsync("op_write_file_async", { + path: pathFromURL(path), + data, + mode: options.mode, + append: options.append ?? false, + create: options.create ?? true, + cancelRid, + }); } finally { - file.close(); + if (options.signal) { + options.signal[abortSignal.remove](abortHandler); + + // always throw the abort error when aborted + options.signal.throwIfAborted(); + } } } diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs index a3d316ae7..3e174e00b 100644 --- a/runtime/ops/fs.rs +++ b/runtime/ops/fs.rs @@ -9,6 +9,9 @@ use deno_core::error::custom_error; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::op; +use deno_core::CancelFuture; +use deno_core::CancelHandle; +use deno_core::ZeroCopyBuf; use deno_core::Extension; use deno_core::OpState; @@ -23,6 +26,7 @@ use std::cell::RefCell; use std::convert::From; use std::env::{current_dir, set_current_dir, temp_dir}; use std::io; +use std::io::Write; use std::io::{Error, Seek, SeekFrom}; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -40,6 +44,8 @@ pub fn init() -> Extension { .ops(vec![ op_open_sync::decl(), op_open_async::decl(), + op_write_file_sync::decl(), + op_write_file_async::decl(), op_seek_sync::decl(), op_seek_async::decl(), op_fdatasync_sync::decl(), @@ -117,7 +123,7 @@ pub struct OpenOptions { fn open_helper( state: &mut OpState, - args: OpenArgs, + args: &OpenArgs, ) -> Result<(PathBuf, std::fs::OpenOptions), AnyError> { let path = Path::new(&args.path).to_path_buf(); @@ -136,7 +142,7 @@ fn open_helper( } let permissions = state.borrow_mut::<Permissions>(); - let options = args.options; + let options = &args.options; if options.read { permissions.read.check(&path)?; @@ -162,7 +168,7 @@ fn op_open_sync( state: &mut OpState, args: OpenArgs, ) -> Result<ResourceId, AnyError> { - let (path, open_options) = open_helper(state, args)?; + let (path, open_options) = open_helper(state, &args)?; let std_file = open_options.open(&path).map_err(|err| { Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) })?; @@ -177,13 +183,14 @@ async fn op_open_async( state: Rc<RefCell<OpState>>, args: OpenArgs, ) -> Result<ResourceId, AnyError> { - let (path, open_options) = open_helper(&mut state.borrow_mut(), args)?; + let (path, open_options) = open_helper(&mut state.borrow_mut(), &args)?; let tokio_file = tokio::fs::OpenOptions::from(open_options) .open(&path) .await .map_err(|err| { Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) })?; + let resource = StdFileResource::fs_file(tokio_file); let rid = state.borrow_mut().resource_table.add(resource); Ok(rid) @@ -191,6 +198,99 @@ async fn op_open_async( #[derive(Deserialize)] #[serde(rename_all = "camelCase")] +pub struct WriteFileArgs { + path: String, + mode: Option<u32>, + append: bool, + create: bool, + data: ZeroCopyBuf, + cancel_rid: Option<ResourceId>, +} + +impl WriteFileArgs { + fn into_open_args_and_data(self) -> (OpenArgs, ZeroCopyBuf) { + ( + OpenArgs { + path: self.path, + mode: self.mode, + options: OpenOptions { + read: false, + write: true, + create: self.create, + truncate: !self.append, + append: self.append, + create_new: false, + }, + }, + self.data, + ) + } +} + +#[op] +fn op_write_file_sync( + state: &mut OpState, + args: WriteFileArgs, +) -> Result<(), AnyError> { + let (open_args, data) = args.into_open_args_and_data(); + let (path, open_options) = open_helper(state, &open_args)?; + write_file(&path, open_options, &open_args, data) +} + +#[op] +async fn op_write_file_async( + state: Rc<RefCell<OpState>>, + args: WriteFileArgs, +) -> Result<(), AnyError> { + let cancel_handle = match args.cancel_rid { + Some(cancel_rid) => state + .borrow_mut() + .resource_table + .get::<CancelHandle>(cancel_rid) + .ok(), + None => None, + }; + let (open_args, data) = args.into_open_args_and_data(); + let (path, open_options) = open_helper(&mut *state.borrow_mut(), &open_args)?; + let write_future = tokio::task::spawn_blocking(move || { + write_file(&path, open_options, &open_args, data) + }); + if let Some(cancel_handle) = cancel_handle { + write_future.or_cancel(cancel_handle).await???; + } else { + write_future.await??; + } + Ok(()) +} + +fn write_file( + path: &Path, + open_options: std::fs::OpenOptions, + _open_args: &OpenArgs, + data: ZeroCopyBuf, +) -> Result<(), AnyError> { + let mut std_file = open_options.open(path).map_err(|err| { + Error::new(err.kind(), format!("{}, open '{}'", err, path.display())) + })?; + + // need to chmod the file if it already exists and a mode is specified + #[cfg(unix)] + if let Some(mode) = &_open_args.mode { + use std::os::unix::fs::PermissionsExt; + let permissions = PermissionsExt::from_mode(mode & 0o777); + std_file + .set_permissions(permissions) + .map_err(|err: Error| { + Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) + })?; + } + + std_file.write_all(&data)?; + Ok(()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SeekArgs { rid: ResourceId, offset: i64, @@ -571,28 +671,12 @@ pub struct ChmodArgs { #[op] fn op_chmod_sync(state: &mut OpState, args: ChmodArgs) -> Result<(), AnyError> { - let path = Path::new(&args.path).to_path_buf(); + let path = Path::new(&args.path); let mode = args.mode & 0o777; - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) - }; - state.borrow_mut::<Permissions>().write.check(&path)?; + state.borrow_mut::<Permissions>().write.check(path)?; debug!("op_chmod_sync {} {:o}", path.display(), mode); - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let permissions = PermissionsExt::from_mode(mode); - std::fs::set_permissions(&path, permissions).map_err(err_mapper)?; - Ok(()) - } - // TODO Implement chmod for Windows (#4357) - #[cfg(not(unix))] - { - // Still check file/dir exists on Windows - let _metadata = std::fs::metadata(&path).map_err(err_mapper)?; - Err(generic_error("Not implemented")) - } + raw_chmod(path, mode) } #[op] @@ -610,28 +694,32 @@ async fn op_chmod_async( tokio::task::spawn_blocking(move || { debug!("op_chmod_async {} {:o}", path.display(), mode); - let err_mapper = |err: Error| { - Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) - }; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let permissions = PermissionsExt::from_mode(mode); - std::fs::set_permissions(&path, permissions).map_err(err_mapper)?; - Ok(()) - } - // TODO Implement chmod for Windows (#4357) - #[cfg(not(unix))] - { - // Still check file/dir exists on Windows - let _metadata = std::fs::metadata(&path).map_err(err_mapper)?; - Err(not_supported()) - } + raw_chmod(&path, mode) }) .await .unwrap() } +fn raw_chmod(path: &Path, _raw_mode: u32) -> Result<(), AnyError> { + let err_mapper = |err: Error| { + Error::new(err.kind(), format!("{}, chmod '{}'", err, path.display())) + }; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let permissions = PermissionsExt::from_mode(_raw_mode); + std::fs::set_permissions(&path, permissions).map_err(err_mapper)?; + Ok(()) + } + // TODO Implement chmod for Windows (#4357) + #[cfg(not(unix))] + { + // Still check file/dir exists on Windows + let _metadata = std::fs::metadata(&path).map_err(err_mapper)?; + Err(not_supported()) + } +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChownArgs { |