mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 13:00:36 -05:00
feat(unstable): Add file locking APIs (#11746)
This commit adds following unstable APIs: - Deno.flock() - Deno.flockSync() - Deno.funlock() - Deno.funlockSync()
This commit is contained in:
parent
46e4ba38b2
commit
93d83a84db
7 changed files with 283 additions and 0 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -828,6 +828,7 @@ dependencies = [
|
||||||
"dlopen",
|
"dlopen",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"filetime",
|
"filetime",
|
||||||
|
"fs3",
|
||||||
"fwdansi",
|
"fwdansi",
|
||||||
"http",
|
"http",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
@ -1294,6 +1295,17 @@ dependencies = [
|
||||||
"syn 1.0.65",
|
"syn 1.0.65",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
|
|
26
cli/dts/lib.deno.ns.d.ts
vendored
26
cli/dts/lib.deno.ns.d.ts
vendored
|
@ -787,6 +787,32 @@ declare namespace Deno {
|
||||||
*/
|
*/
|
||||||
export function fdatasync(rid: number): Promise<void>;
|
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
|
/** 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
|
* as via opening or creating a file. Closing a file when you are finished
|
||||||
* with it is important to avoid leaking resources.
|
* with it is important to avoid leaking resources.
|
||||||
|
|
102
cli/tests/unit/flock_test.ts
Normal file
102
cli/tests/unit/flock_test.ts
Normal file
|
@ -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",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
|
@ -65,6 +65,7 @@ atty = "0.2.14"
|
||||||
dlopen = "0.1.8"
|
dlopen = "0.1.8"
|
||||||
encoding_rs = "0.8.28"
|
encoding_rs = "0.8.28"
|
||||||
filetime = "0.2.14"
|
filetime = "0.2.14"
|
||||||
|
fs3 = "0.5.0"
|
||||||
http = "0.2.4"
|
http = "0.2.4"
|
||||||
hyper = { version = "0.14.10", features = ["server", "stream", "http1", "http2", "runtime"] }
|
hyper = { version = "0.14.10", features = ["server", "stream", "http1", "http2", "runtime"] }
|
||||||
# TODO(lucacasonato): unlock when https://github.com/tkaitchuck/aHash/issues/95 is resolved
|
# TODO(lucacasonato): unlock when https://github.com/tkaitchuck/aHash/issues/95 is resolved
|
||||||
|
|
|
@ -385,6 +385,22 @@
|
||||||
await core.opAsync("op_fsync_async", rid);
|
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 = {
|
window.__bootstrap.fs = {
|
||||||
cwd,
|
cwd,
|
||||||
chdir,
|
chdir,
|
||||||
|
@ -433,5 +449,9 @@
|
||||||
fdatasyncSync,
|
fdatasyncSync,
|
||||||
fsync,
|
fsync,
|
||||||
fsyncSync,
|
fsyncSync,
|
||||||
|
flock,
|
||||||
|
flockSync,
|
||||||
|
funlock,
|
||||||
|
funlockSync,
|
||||||
};
|
};
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -136,5 +136,9 @@
|
||||||
createHttpClient: __bootstrap.fetch.createHttpClient,
|
createHttpClient: __bootstrap.fetch.createHttpClient,
|
||||||
http: __bootstrap.http,
|
http: __bootstrap.http,
|
||||||
dlopen: __bootstrap.ffi.dlopen,
|
dlopen: __bootstrap.ffi.dlopen,
|
||||||
|
flock: __bootstrap.fs.flock,
|
||||||
|
flockSync: __bootstrap.fs.flockSync,
|
||||||
|
funlock: __bootstrap.fs.funlock,
|
||||||
|
funlockSync: __bootstrap.fs.funlockSync,
|
||||||
};
|
};
|
||||||
})(this);
|
})(this);
|
||||||
|
|
|
@ -48,6 +48,10 @@ pub fn init() -> Extension {
|
||||||
("op_fsync_async", op_async(op_fsync_async)),
|
("op_fsync_async", op_async(op_fsync_async)),
|
||||||
("op_fstat_sync", op_sync(op_fstat_sync)),
|
("op_fstat_sync", op_sync(op_fstat_sync)),
|
||||||
("op_fstat_async", op_async(op_fstat_async)),
|
("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_umask", op_sync(op_umask)),
|
||||||
("op_chdir", op_sync(op_chdir)),
|
("op_chdir", op_sync(op_chdir)),
|
||||||
("op_mkdir_sync", op_sync(op_mkdir_sync)),
|
("op_mkdir_sync", op_sync(op_mkdir_sync)),
|
||||||
|
@ -346,6 +350,120 @@ async fn op_fstat_async(
|
||||||
Ok(get_stat(metadata))
|
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(
|
fn op_umask(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
mask: Option<u32>,
|
mask: Option<u32>,
|
||||||
|
|
Loading…
Add table
Reference in a new issue