-
Notifications
You must be signed in to change notification settings - Fork 698
feat(services): Add wasi-fs backend using wasi:filesystem interface #7005
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add complete wasi-fs service implementation: - core.rs: WASI filesystem operations via preopened directories - reader.rs, writer.rs: streaming I/O via wasi:io/streams - lister.rs, deleter.rs: directory operations - Integrated into workspace with services-wasi-fs feature flag
- Fix ErrorKind variants (removed ContentTooLarge, InvalidInput, IsFull) - Fix Descriptor cloning (use swap_remove for ownership transfer) - Fix Timestamp creation (use new() instead of from_unix_nanos) - Fix BytesRange API (use offset() and size() methods) - Fix async recursion in lister (use loop instead) - Fix web_time cfg in opendal-core for WASI target
The wasi-fs crate compiles to nothing on non-WASI targets due to its platform cfg gate. This caused an unused import warning when clippy runs with --all-features on native targets. Fix by adding target_arch and target_os conditions to the re-export.
Replace external actions not allowed in Apache repository: - dtolnay/rust-action → ./.github/actions/setup - bytecodealliance/actions/wasmtime/setup → manual wasmtime install Use wasmtime v39.0.1 (latest stable).
Tokio doesn't support wasm32-wasip2 target, so behavior tests cannot run. Change CI to verify the service compiles correctly instead. Remove the service action from .github/services/ to prevent the behavior test matrix from attempting to run wasi-fs tests.
WASI Usability InvestigationWhile implementing integration tests, I discovered a fundamental architectural issue with using the wasi-fs service. The ProblemThe wasi-fs service implementation is correct -
// From core/core/src/blocking/operator.rs
pub struct Operator {
handle: tokio::runtime::Handle, // Requires tokio!
op: AsyncOperator,
}
impl Operator {
pub fn new(op: AsyncOperator) -> Result<Self> {
Ok(Self {
handle: Handle::try_current() // Needs tokio runtime
.map_err(|_| Error::new(...))?,
op,
})
}
}Tokio doesn't compile for Current State
Possible Solutions
Questions for Maintainers
The service implementation is complete and correct, but without a usable API, it's effectively dead code. |
Update: WASIp3 is Now AvailableAfter further investigation, WASI 0.3 (WASIp3) with native async support is now available:
What This MeansInstead of building workarounds for WASIp2's lack of async, the wasi-fs service could be updated to target WASIp3:
RecommendationConsider updating wasi-fs to target WASIp3 instead of building a separate
The tradeoff is that WASIp3 is newer, so runtime support may be less widespread than WASIp2. However, Wasmtime (the most common WASI runtime) already supports it. References: |
Xuanwo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for working on this!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to not use scripts. We can just move those logic to github workflows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to not use scripts. We can just move those logic to github workflows.
@Xuanwo you mean inlining the script into the github workflow?
core/services/wasi-fs/src/core.rs
Outdated
| .map_err(parse_wasi_error) | ||
| } | ||
|
|
||
| pub fn copy(&self, from: &str, to: &str) -> Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to manually simulate copying if it lacks native copy support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I'll remove the custom copy impl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I'll remove the custom copy impl.
Just to add a bit more context:
- WASI doesn't have native copy (no
copy_file_rangeequivalent) - OpenDAL doesn't provide a copy fallback - if a backend doesn't support copy, users get an error
- The
fsservice usestokio::fs::copywhich delegates to the OS (which may usecopy_file_rangeon Linux or a read/write loop internally - but that's the OS's responsibility, not OpenDAL's)
IMHO, if point 3 stands for fs, it does sort of stand for wasi too. But the lack of customizable buffer size is a problem all by itself. Which is a good enough a reason to keep this a user level job and remove copy().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 01f2bec
core/services/wasi-fs/src/lister.rs
Outdated
| let name = entry.name; | ||
|
|
||
| // Skip . and .. entries | ||
| if name == "." || name == ".." { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can just return self here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review! I investigated the WASI Preview 2 specification.
From the wasi-filesystem WIT spec, the read-directory function explicitly states:
"On filesystems where directories contain entries referring to themselves and their parents, often named
.and..respectively, these entries are omitted."
This is different from Preview 1 which included them. So:
- The
.and..check is actually dead code - they'll never be returned - The
returned_selfapproach is necessary since we won't get.from WASI - The suggestion to "return self here" won't work because
.never appears
I'll remove the unnecessary ./.. check but keep the returned_self logic.
Disclosure: This analysis was performed with assistance from Claude Code (AI).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 681baa1 - removed the unnecessary check and simplified the match expression.
| let data: Vec<u8> = bs.to_vec(); | ||
|
|
||
| self.stream | ||
| .blocking_write_and_flush(&data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only have blocking API here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, WASI Preview 2 only provides blocking APIs - this is a platform limitation.
Comparison
| Standard OpenDAL | WASIp2 (current) | WASIp3 (future) | |
|---|---|---|---|
| Async Runtime | tokio | None (blocking only) | Component Model native (future<T>, stream<T>) |
| Operator | Operator |
Needs WasiOperator (blocking) |
WasiOperator (async) |
| Compiles for wasm32-wasip2 | ❌ | ✅ | N/A |
| Compiles for wasm32-wasip3 | ❌ | ✅ | ✅ |
Current State (WASIp2)
To make wasi-fs usable at the Operator level today, OpenDAL would need a WasiOperator that doesn't depend on tokio. This operator would use blocking calls internally since that's all WASIp2 offers.
Future Enhancement (WASIp3)
WASI 0.3 (available in Wasmtime v39+) introduces native async support via future<T> and stream<T> in the Component Model. A future WasiOperator could leverage WASIp3's async primitives instead of blocking calls, aligning with OpenDAL's async-first architecture without requiring tokio.
Disclosure: This response was drafted with assistance from Claude Code (AI).
WASI Preview 2 lacks native copy support. Per OpenDAL's design philosophy, services should only advertise capabilities they natively support rather than emulating them via read+write.
WASI Preview 2 spec explicitly omits . and .. entries from read-directory, so the check is dead code. Also simplified the match expression by removing the loop (no longer needed without the continue statement).
Summary
Adds a new
wasi-fsbackend that implements filesystem access via thewasi:filesysteminterface for WebAssembly components running in WASI Preview 2 runtimes (wasmtime, wasmer).Closes #7004
AI Assistance Disclosure
This implementation was developed with assistance from Claude (Anthropic). The AI helped with:
Design Decisions
1. Crate Architecture
Decision: Split crate under
core/services/wasi-fs/(not inline in core)Rationale: Follows OpenDAL's modern service pattern (RFC 6828). Services are being migrated to separate crates for minimal compile-time impact and independent versioning.
2. WASI Bindings Approach
Decision: Use the
wasicrate (v0.14.7) with pre-generated bindingsRationale:
wit-bindgenduring compilation3. Async Model
Decision: Wrap synchronous WASI calls directly in async methods
Rationale:
spawn_blockingneeded since there's no separate thread pool in WASM4. Preopened Directory Handling
Decision: Match root path to preopened directories from host runtime
Rationale:
/or not specified5. Platform Restriction
Decision: Entire crate guarded by
#![cfg(all(target_arch = "wasm32", target_os = "wasi"))]Rationale:
wasm32-wasip2targetImplementation
Files Created
Capabilities
Limitations
abort()returns error since WASI doesn't provide atomic file operationswasm32-wasip2targetTest Plan
cargo checkpasses on native targetcargo check --features services-wasi-fspassescargo check --target wasm32-wasip2 -p opendal-service-wasi-fspassescargo fmt --allpasses.github/workflows/test_wasi_fs.yml)Testing Infrastructure Added
.env.example- Added wasi-fs configuration template.github/services/wasi_fs/wasi_fs/action.yml- CI service setup action.github/workflows/test_wasi_fs.yml- Dedicated CI workflow for wasi-fscore/scripts/test_wasi_fs.sh- Local test runner scriptRunning Tests Locally
Known Testing Limitation
The standard OpenDAL behavior test framework (
cargo test behavior) uses tokio which doesn't compile forwasm32-wasip2. The CI workflow and test runner script are prepared for when WASI-compatible test infrastructure becomes available.Fixes Included
web_timecfg conditions inopendal-core/src/raw/time.rsto properly distinguish browser WASM (target_os = "unknown") from WASI (target_os = "wasi")Usage Example
# Runtime invocation wasmtime run --dir /host/path::/guest/path component.wasm