perf: move Deno.writeTextFile and like functions to Rust (#14221)

Co-authored-by: Luca Casonato <hello@lcas.dev>
This commit is contained in:
David Sherret 2022-04-18 18:00:14 -04:00 committed by GitHub
parent ca3b20df3c
commit a64e63c361
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 222 additions and 138 deletions

View file

@ -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);
}
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 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);
}
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();
}
}
}

View file

@ -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,18 +183,112 @@ 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)
}
#[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 {
@ -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 {