summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock12
-rw-r--r--cli/dts/lib.deno.ns.d.ts26
-rw-r--r--cli/tests/unit/flock_test.ts102
-rw-r--r--runtime/Cargo.toml1
-rw-r--r--runtime/js/30_fs.js20
-rw-r--r--runtime/js/90_deno_ns.js4
-rw-r--r--runtime/ops/fs.rs118
7 files changed, 283 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 81e8ef78e..f3189660b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -828,6 +828,7 @@ dependencies = [
"dlopen",
"encoding_rs",
"filetime",
+ "fs3",
"fwdansi",
"http",
"hyper",
@@ -1295,6 +1296,17 @@ dependencies = [
]
[[package]]
+name = "fs3"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb17cf6ed704f72485332f6ab65257460c4f9f3083934cf402bf9f5b3b600a90"
+dependencies = [
+ "libc",
+ "rustc_version 0.2.3",
+ "winapi 0.3.9",
+]
+
+[[package]]
name = "fsevent-sys"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/cli/dts/lib.deno.ns.d.ts b/cli/dts/lib.deno.ns.d.ts
index 420a08da4..996aff363 100644
--- a/cli/dts/lib.deno.ns.d.ts
+++ b/cli/dts/lib.deno.ns.d.ts
@@ -787,6 +787,32 @@ declare namespace Deno {
*/
export function fdatasync(rid: number): Promise<void>;
+ /** **UNSTABLE**: New API should be tested first.
+ *
+ * Acquire an advisory file-system lock for the provided file. `exclusive`
+ * defaults to `false`.
+ */
+ export function flock(rid: number, exclusive?: boolean): Promise<void>;
+
+ /** **UNSTABLE**: New API should be tested first.
+ *
+ * Acquire an advisory file-system lock for the provided file. `exclusive`
+ * defaults to `false`.
+ */
+ export function flockSync(rid: number, exclusive?: boolean): void;
+
+ /** **UNSTABLE**: New API should be tested first.
+ *
+ * Release an advisory file-system lock for the provided file.
+ */
+ export function funlock(rid: number): Promise<void>;
+
+ /** **UNSTABLE**: New API should be tested first.
+ *
+ * Release an advisory file-system lock for the provided file.
+ */
+ export function funlockSync(rid: number): void;
+
/** Close the given resource ID (rid) which has been previously opened, such
* as via opening or creating a file. Closing a file when you are finished
* with it is important to avoid leaking resources.
diff --git a/cli/tests/unit/flock_test.ts b/cli/tests/unit/flock_test.ts
new file mode 100644
index 000000000..13d09bcf5
--- /dev/null
+++ b/cli/tests/unit/flock_test.ts
@@ -0,0 +1,102 @@
+// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
+import { assertEquals, unitTest } from "./test_util.ts";
+
+unitTest(
+ { perms: { read: true, run: true, hrtime: true } },
+ async function flockFileSync() {
+ const path = "cli/tests/testdata/fixture.json";
+ const script = (exclusive: boolean, wait: number) => `
+ const { rid } = Deno.openSync("${path}");
+ Deno.flockSync(rid, ${exclusive ? "true" : "false"});
+ await new Promise(res => setTimeout(res, ${wait}));
+ Deno.funlockSync(rid);
+ `;
+ const run = (e: boolean, w: number) =>
+ Deno.run({ cmd: [Deno.execPath(), "eval", "--unstable", script(e, w)] });
+ const firstBlocksSecond = async (
+ first: boolean,
+ second: boolean,
+ ): Promise<boolean> => {
+ const firstPs = run(first, 1000);
+ await new Promise((res) => setTimeout(res, 250));
+ const start = performance.now();
+ const secondPs = run(second, 0);
+ await secondPs.status();
+ const didBlock = (performance.now() - start) > 500;
+ firstPs.close();
+ secondPs.close();
+ return didBlock;
+ };
+
+ assertEquals(
+ await firstBlocksSecond(true, false),
+ true,
+ "exclusive blocks shared",
+ );
+ assertEquals(
+ await firstBlocksSecond(false, true),
+ true,
+ "shared blocks exclusive",
+ );
+ assertEquals(
+ await firstBlocksSecond(true, true),
+ true,
+ "exclusive blocks exclusive",
+ );
+ assertEquals(
+ await firstBlocksSecond(false, false),
+ false,
+ "shared does not block shared",
+ );
+ },
+);
+
+unitTest(
+ { perms: { read: true, run: true, hrtime: true } },
+ async function flockFileAsync() {
+ const path = "cli/tests/testdata/fixture.json";
+ const script = (exclusive: boolean, wait: number) => `
+ const { rid } = await Deno.open("${path}");
+ await Deno.flock(rid, ${exclusive ? "true" : "false"});
+ await new Promise(res => setTimeout(res, ${wait}));
+ await Deno.funlock(rid);
+ `;
+ const run = (e: boolean, w: number) =>
+ Deno.run({ cmd: [Deno.execPath(), "eval", "--unstable", script(e, w)] });
+ const firstBlocksSecond = async (
+ first: boolean,
+ second: boolean,
+ ): Promise<boolean> => {
+ const firstPs = run(first, 1000);
+ await new Promise((res) => setTimeout(res, 250));
+ const start = performance.now();
+ const secondPs = run(second, 0);
+ await secondPs.status();
+ const didBlock = (performance.now() - start) > 500;
+ firstPs.close();
+ secondPs.close();
+ return didBlock;
+ };
+
+ assertEquals(
+ await firstBlocksSecond(true, false),
+ true,
+ "exclusive blocks shared",
+ );
+ assertEquals(
+ await firstBlocksSecond(false, true),
+ true,
+ "shared blocks exclusive",
+ );
+ assertEquals(
+ await firstBlocksSecond(true, true),
+ true,
+ "exclusive blocks exclusive",
+ );
+ assertEquals(
+ await firstBlocksSecond(false, false),
+ false,
+ "shared does not block shared",
+ );
+ },
+);
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 491516dbb..520509450 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -65,6 +65,7 @@ atty = "0.2.14"
dlopen = "0.1.8"
encoding_rs = "0.8.28"
filetime = "0.2.14"
+fs3 = "0.5.0"
http = "0.2.4"
hyper = { version = "0.14.10", features = ["server", "stream", "http1", "http2", "runtime"] }
# TODO(lucacasonato): unlock when https://github.com/tkaitchuck/aHash/issues/95 is resolved
diff --git a/runtime/js/30_fs.js b/runtime/js/30_fs.js
index e45cda321..feb9f8f54 100644
--- a/runtime/js/30_fs.js
+++ b/runtime/js/30_fs.js
@@ -385,6 +385,22 @@
await core.opAsync("op_fsync_async", rid);
}
+ function flockSync(rid, exclusive) {
+ core.opSync("op_flock_sync", rid, exclusive === true);
+ }
+
+ async function flock(rid, exclusive) {
+ await core.opAsync("op_flock_async", rid, exclusive === true);
+ }
+
+ function funlockSync(rid) {
+ core.opSync("op_funlock_sync", rid);
+ }
+
+ async function funlock(rid) {
+ await core.opAsync("op_funlock_async", rid);
+ }
+
window.__bootstrap.fs = {
cwd,
chdir,
@@ -433,5 +449,9 @@
fdatasyncSync,
fsync,
fsyncSync,
+ flock,
+ flockSync,
+ funlock,
+ funlockSync,
};
})(this);
diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js
index 796361d7a..71c8bd0f0 100644
--- a/runtime/js/90_deno_ns.js
+++ b/runtime/js/90_deno_ns.js
@@ -136,5 +136,9 @@
createHttpClient: __bootstrap.fetch.createHttpClient,
http: __bootstrap.http,
dlopen: __bootstrap.ffi.dlopen,
+ flock: __bootstrap.fs.flock,
+ flockSync: __bootstrap.fs.flockSync,
+ funlock: __bootstrap.fs.funlock,
+ funlockSync: __bootstrap.fs.funlockSync,
};
})(this);
diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs
index 419e41718..819f3f3ac 100644
--- a/runtime/ops/fs.rs
+++ b/runtime/ops/fs.rs
@@ -48,6 +48,10 @@ pub fn init() -> Extension {
("op_fsync_async", op_async(op_fsync_async)),
("op_fstat_sync", op_sync(op_fstat_sync)),
("op_fstat_async", op_async(op_fstat_async)),
+ ("op_flock_sync", op_sync(op_flock_sync)),
+ ("op_flock_async", op_async(op_flock_async)),
+ ("op_funlock_sync", op_sync(op_funlock_sync)),
+ ("op_funlock_async", op_async(op_funlock_async)),
("op_umask", op_sync(op_umask)),
("op_chdir", op_sync(op_chdir)),
("op_mkdir_sync", op_sync(op_mkdir_sync)),
@@ -346,6 +350,120 @@ async fn op_fstat_async(
Ok(get_stat(metadata))
}
+fn op_flock_sync(
+ state: &mut OpState,
+ rid: ResourceId,
+ exclusive: bool,
+) -> Result<(), AnyError> {
+ use fs3::FileExt;
+ super::check_unstable(state, "Deno.flockSync");
+
+ StdFileResource::with(state, rid, |r| match r {
+ Ok(std_file) => {
+ if exclusive {
+ std_file.lock_exclusive()?;
+ } else {
+ std_file.lock_shared()?;
+ }
+ Ok(())
+ }
+ Err(_) => Err(type_error("cannot lock this type of resource".to_string())),
+ })
+}
+
+async fn op_flock_async(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ exclusive: bool,
+) -> Result<(), AnyError> {
+ use fs3::FileExt;
+ super::check_unstable2(&state, "Deno.flock");
+
+ let resource = state
+ .borrow_mut()
+ .resource_table
+ .get::<StdFileResource>(rid)?;
+
+ if resource.fs_file.is_none() {
+ return Err(bad_resource_id());
+ }
+
+ let mut fs_file = RcRef::map(&resource, |r| r.fs_file.as_ref().unwrap())
+ .borrow_mut()
+ .await;
+
+ let std_file = (*fs_file)
+ .0
+ .as_mut()
+ .unwrap()
+ .try_clone()
+ .await?
+ .into_std()
+ .await;
+ tokio::task::spawn_blocking(move || -> Result<(), AnyError> {
+ if exclusive {
+ std_file.lock_exclusive()?;
+ } else {
+ std_file.lock_shared()?;
+ }
+ Ok(())
+ })
+ .await?
+}
+
+fn op_funlock_sync(
+ state: &mut OpState,
+ rid: ResourceId,
+ _: (),
+) -> Result<(), AnyError> {
+ use fs3::FileExt;
+ super::check_unstable(state, "Deno.funlockSync");
+
+ StdFileResource::with(state, rid, |r| match r {
+ Ok(std_file) => {
+ std_file.unlock()?;
+ Ok(())
+ }
+ Err(_) => Err(type_error("cannot lock this type of resource".to_string())),
+ })
+}
+
+async fn op_funlock_async(
+ state: Rc<RefCell<OpState>>,
+ rid: ResourceId,
+ _: (),
+) -> Result<(), AnyError> {
+ use fs3::FileExt;
+ super::check_unstable2(&state, "Deno.funlock");
+
+ let resource = state
+ .borrow_mut()
+ .resource_table
+ .get::<StdFileResource>(rid)?;
+
+ if resource.fs_file.is_none() {
+ return Err(bad_resource_id());
+ }
+
+ let mut fs_file = RcRef::map(&resource, |r| r.fs_file.as_ref().unwrap())
+ .borrow_mut()
+ .await;
+
+ let std_file = (*fs_file)
+ .0
+ .as_mut()
+ .unwrap()
+ .try_clone()
+ .await?
+ .into_std()
+ .await;
+ tokio::task::spawn_blocking(move || -> Result<(), AnyError> {
+ std_file.unlock()?;
+ Ok(())
+ })
+ .await?
+}
+
fn op_umask(
state: &mut OpState,
mask: Option<u32>,