Skip to content

Commit

Permalink
0.10.0 (#27)
Browse files Browse the repository at this point in the history
* Add `*_async` APIs
  • Loading branch information
al8n authored Oct 8, 2024
1 parent a96c0e4 commit 58cc3f2
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
github: al8n
patreon: al8n
ko_fi: al8n9434
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 50

- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every weekday
interval: "daily"
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@ jobs:
- name: test all features
# Run sequentially to avoid race condition around file system size
run: cargo test --all-features -- --test-threads 1
docs:
name: docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache cargo build and registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ubuntu-latest-docs-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
ubuntu-latest-docs-
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.nightly }}
override: true
- name: "doc --lib --all-features"
run: cargo doc --lib --no-deps --all-features
env:
RUSTFLAGS: --cfg docsrs
RUSTDOCFLAGS: --cfg docsrs -Dwarnings

coverage:
name: cargo tarpaulin
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "fs4"
# NB: When modifying, also modify html_root_url in lib.rs
version = "0.9.1"
version = "0.10.0"
rust-version = "1.75.0"
authors = ["Dan Burkert <dan@danburkert.com>", "Al Liu <scygliu1@gmail.com>"]
license = "MIT OR Apache-2.0"
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,37 @@ This is a fork of the [fs2-rs](https://github.com/danburkert/fs2-rs) crate, the
- std
```toml
[dependencies]
fs4 = { version = "0.9", features = ["sync"] }
fs4 = { version = "0.10", features = ["sync"] }
```

- [async-std runtime](https://crates.io/crates/async-std)
```toml
[dependencies]
fs4 = { version = "0.9", features = ["async-std"] }
fs4 = { version = "0.10", features = ["async-std"] }
```

- [fs-err](https://crates.io/crates/fs-err)
```toml
[dependencies]
fs4 = { version = "0.9", features = ["fs-err"] }
fs4 = { version = "0.10", features = ["fs-err"] }
```

- [fs-err-tokio](https://crates.io/crates/fs-err)
```toml
[dependencies]
fs4 = { version = "0.9", features = ["fs-err-tokio"] }
fs4 = { version = "0.10", features = ["fs-err-tokio"] }
```

- [smol runtime](https://crates.io/crates/smol)
```toml
[dependencies]
fs4 = { version = "0.9", features = ["smol"] }
fs4 = { version = "0.10", features = ["smol"] }
```

- [tokio runtime](https://crates.io/crates/tokio)
```toml
[dependencies]
fs4 = { version = "0.9", features = ["tokio"] }
fs4 = { version = "0.10", features = ["tokio"] }
```

## Features
Expand Down
170 changes: 170 additions & 0 deletions src/file_ext/async_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,54 @@ macro_rules! async_file_ext {
/// locked exclusively.
fn lock_shared(&self) -> Result<()>;

/// Locks the file for exclusive usage, blocking if the file is currently
/// locked exclusively.
///
/// **Note:** This method is not really "async", the underlying system call is still blocking.
/// Having this method as async is just for convenience when using it in async runtime.
fn lock_shared_async(&self) -> impl core::future::Future<Output = Result<()>>;

/// Locks the file for exclusive usage, blocking if the file is currently
/// locked.
fn lock_exclusive(&self) -> Result<()>;

/// Locks the file for exclusive usage, blocking if the file is currently
/// locked.
///
/// **Note:** This method is not really "async", the underlying system call is still blocking.
/// Having this method as async is just for convenience when using it in async runtime.
fn lock_exclusive_async(&self) -> impl core::future::Future<Output = Result<()>>;

/// Locks the file for shared usage, or returns an error if the file is
/// currently locked (see `lock_contended_error`).
fn try_lock_shared(&self) -> Result<()>;

/// Locks the file for shared usage, or returns an error if the file is
/// currently locked (see `lock_contended_error`).
///
/// **Note:** This method is not really "async", the underlying system call is still blocking.
/// Having this method as async is just for convenience when using it in async runtime.
fn try_lock_shared_async(&self) -> impl core::future::Future<Output = Result<()>>;

/// Locks the file for exclusive usage, or returns an error if the file is
/// currently locked (see `lock_contended_error`).
fn try_lock_exclusive(&self) -> Result<()>;

/// Locks the file for exclusive usage, or returns an error if the file is
/// currently locked (see `lock_contended_error`).
///
/// **Note:** This method is not really "async", the underlying system call is still blocking.
/// Having this method as async is just for convenience when using it in async runtime.
fn try_lock_exclusive_async(&self) -> impl core::future::Future<Output = Result<()>>;

/// Unlocks the file.
fn unlock(&self) -> Result<()>;

/// Unlocks the file.
///
/// **Note:** This method is not really "async", the underlying system call is still blocking.
/// Having this method as async is just for convenience when using it in async runtime.
fn unlock_async(&self) -> impl core::future::Future<Output = Result<()>>;
}

impl AsyncFileExt for $file {
Expand All @@ -68,18 +102,42 @@ macro_rules! async_file_ext {
fn lock_shared(&self) -> Result<()> {
sys::lock_shared(self)
}

async fn lock_shared_async(&self) -> Result<()> {
sys::lock_shared(self)
}

fn lock_exclusive(&self) -> Result<()> {
sys::lock_exclusive(self)
}

async fn lock_exclusive_async(&self) -> Result<()> {
sys::lock_exclusive(self)
}

fn try_lock_shared(&self) -> Result<()> {
sys::try_lock_shared(self)
}

async fn try_lock_shared_async(&self) -> Result<()> {
sys::try_lock_shared(self)
}

fn try_lock_exclusive(&self) -> Result<()> {
sys::try_lock_exclusive(self)
}

async fn try_lock_exclusive_async(&self) -> Result<()> {
sys::try_lock_exclusive(self)
}

fn unlock(&self) -> Result<()> {
sys::unlock(self)
}

async fn unlock_async(&self) -> Result<()> {
sys::unlock(self)
}
}
}
}
Expand Down Expand Up @@ -144,6 +202,51 @@ macro_rules! test_mod {
file3.lock_exclusive().unwrap();
}

/// Tests shared file lock operations.
#[$annotation]
async fn lock_shared_async() {
let tempdir = tempdir::TempDir::new("fs4").unwrap();
let path = tempdir.path().join("fs4");
let file1 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.await
.unwrap();
let file2 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.await
.unwrap();
let file3 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.await
.unwrap();

// Concurrent shared access is OK, but not shared and exclusive.
file1.lock_shared_async().await.unwrap();
file2.lock_shared_async().await.unwrap();
assert_eq!(
file3.try_lock_exclusive_async().await.unwrap_err().kind(),
lock_contended_error().kind()
);
file1.unlock_async().await.unwrap();
assert_eq!(
file3.try_lock_exclusive_async().await.unwrap_err().kind(),
lock_contended_error().kind()
);

// Once all shared file locks are dropped, an exclusive lock may be created;
file2.unlock_async().await.unwrap();
file3.lock_exclusive_async().await.unwrap();
}

/// Tests exclusive file lock operations.
#[$annotation]
async fn lock_exclusive() {
Expand Down Expand Up @@ -180,6 +283,42 @@ macro_rules! test_mod {
file2.lock_exclusive().unwrap();
}

/// Tests exclusive file lock operations.
#[$annotation]
async fn lock_exclusive_async() {
let tempdir = tempdir::TempDir::new("fs4").unwrap();
let path = tempdir.path().join("fs4");
let file1 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.await
.unwrap();
let file2 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.await
.unwrap();

// No other access is possible once an exclusive lock is created.
file1.lock_exclusive_async().await.unwrap();
assert_eq!(
file2.try_lock_exclusive_async().await.unwrap_err().kind(),
lock_contended_error().kind()
);
assert_eq!(
file2.try_lock_shared_async().await.unwrap_err().kind(),
lock_contended_error().kind()
);

// Once the exclusive lock is dropped, the second file is able to create a lock.
file1.unlock_async().await.unwrap();
file2.lock_exclusive_async().await.unwrap();
}

/// Tests that a lock is released after the file that owns it is dropped.
#[$annotation]
async fn lock_cleanup() {
Expand Down Expand Up @@ -211,6 +350,37 @@ macro_rules! test_mod {
file2.lock_shared().unwrap();
}

/// Tests that a lock is released after the file that owns it is dropped.
#[$annotation]
async fn lock_cleanup_async() {
let tempdir = tempdir::TempDir::new("fs4").unwrap();
let path = tempdir.path().join("fs4");
let file1 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.await
.unwrap();
let file2 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.await
.unwrap();

file1.lock_exclusive_async().await.unwrap();
assert_eq!(
file2.try_lock_shared_async().await.unwrap_err().kind(),
lock_contended_error().kind()
);

// Drop file1; the lock should be released.
drop(file1);
file2.lock_shared_async().await.unwrap();
}

/// Tests file allocation.
#[$annotation]
async fn allocate() {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Extended utilities for working with files and filesystems in Rust.
#![doc(html_root_url = "https://docs.rs/fs4/0.9.1")]
#![doc(html_root_url = "https://docs.rs/fs4/0.10.0")]
#![cfg_attr(test, feature(test))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]
Expand Down

0 comments on commit 58cc3f2

Please sign in to comment.