1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -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:
Tilman Roeder 2021-08-24 14:21:31 +01:00 committed by GitHub
parent 46e4ba38b2
commit 93d83a84db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 283 additions and 0 deletions

12
Cargo.lock generated
View file

@ -828,6 +828,7 @@ dependencies = [
"dlopen",
"encoding_rs",
"filetime",
"fs3",
"fwdansi",
"http",
"hyper",
@ -1294,6 +1295,17 @@ dependencies = [
"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]]
name = "fsevent-sys"
version = "4.0.0"

View file

@ -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.

View 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",
);
},
);

View file

@ -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

View file

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

View file

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

View file

@ -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>,