Skip to content

Add WASM32 core platform abstractions #5079

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

Closed
wants to merge 1 commit into from

Conversation

zzorba
Copy link
Contributor

@zzorba zzorba commented May 22, 2025

This commit adds foundational WASM32 support to matrix-sdk-common by introducing platform-agnostic abstractions for async operations:

  • async_lock.rs: WASM32-compatible async RwLock implementation using std::sync primitives with async interface
  • stream.rs: WASM32 stream utilities using LocalBoxStream instead of BoxStream
  • executor.rs: Enhanced with comprehensive WASM runtime abstraction including WasmRuntimeHandle, unified Handle/Runtime types, and improved JoinError handling
  • Dependencies: Added futures-executor for WASM32 and async-compat for non-WASM32 targets

These abstractions enable matrix-rust-sdk to run on both WASM32 and native targets without conditional compilation in consuming code.

This is part of a larger effort by Filament to add wasm32 support to the matrix-rust-sdk and matrix-sdk-ffi bindings. We have these bindings working on our fork, and are now taking steps to cherry pick them incrementally back to main to make them more reviewable.

  • Public API changes documented in changelogs (optional)

Signed-off-by: Daniel Salinas

@zzorba zzorba requested a review from a team as a code owner May 22, 2025 22:57
@zzorba zzorba requested review from Hywan and removed request for a team May 22, 2025 22:57
@zzorba zzorba force-pushed the wasm32-core-abstractions branch 2 times, most recently from 0823d50 to ec94b8c Compare May 22, 2025 23:10
Copy link

codecov bot commented May 22, 2025

Codecov Report

Attention: Patch coverage is 0% with 49 lines in your changes missing coverage. Please review.

Project coverage is 85.75%. Comparing base (4d027ec) to head (ec94b8c).
Report is 153 commits behind head on main.

Files with missing lines Patch % Lines
crates/matrix-sdk-common/src/async_lock.rs 0.00% 28 Missing ⚠️
crates/matrix-sdk-common/src/executor.rs 0.00% 13 Missing ⚠️
crates/matrix-sdk-common/src/stream.rs 0.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5079      +/-   ##
==========================================
- Coverage   85.84%   85.75%   -0.09%     
==========================================
  Files         325      327       +2     
  Lines       35906    35954      +48     
==========================================
+ Hits        30824    30834      +10     
- Misses       5082     5120      +38     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a lot of #[cfg(target_arch = "wasm32")]s. How about doing it like this:

#[cfg(not(target_arch = "wasm32"))]
pub use tokio::sync::{ ... };

#[cfg(target_arch = "wasm32")]
pub use self::async_lock_wasm::*;

#[cfg(target_arch = "wasm32")]
mod async_lock_wasm {
    // all the current cfg'd items
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, this. A similar approach would be:

#[cfg(not(target_family = "wasm"))]
mod sys {
    pub use tokio::sync::{};
}

#[cfg(target_family = "wasm"))]
mod sys {
    pub use;
}

pub use sys::*;

I would prefer this choice to be honest.

Also, please use target_family = "wasm" if possible (see target_family) rather than target_arch = "wasm32". wasm64 will come soon, and it will be a maintenance burden.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this thread is irrelevant given the other one on this file ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will make these corrections in these shim files. It seemed like the other was the norm but happy to adjust it.

Comment on lines 219 to 227
#[cfg(target_arch = "wasm32")]
{
WasmRuntimeHandle
}

#[cfg(not(target_arch = "wasm32"))]
{
async_compat::get_runtime_handle()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[cfg(target_arch = "wasm32")]
{
WasmRuntimeHandle
}
#[cfg(not(target_arch = "wasm32"))]
{
async_compat::get_runtime_handle()
}
#[cfg(target_arch = "wasm32")]
return WasmRuntimeHandle;
#[cfg(not(target_arch = "wasm32"))]
return async_compat::get_runtime_handle();

Comment on lines 17 to 20
/// Future for the [`next`](super::StreamExt::next) method.
#[cfg(target_arch = "wasm32")]
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Next<'a, St: ?Sized> {
stream: &'a mut St,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed? I think futures_util::stream::StreamExt's next should just work

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is the call to .boxed() on the stream it returns, the StreamExt has has a Send trait requirement that prevents .boxed() from being called when used in situations like this:

        let (initial, mut subscriber): (RoomPaginationStatus, BoxStream<'_, RoomPaginationStatus>) =
            match self.inner.live_back_pagination_status().await {
                Some((status, stream)) => (status, stream.boxed()),
                None => {
                    let (status, stream) = self.inner.focused_pagination_status().await.context(
                        "can't subscribe to the back-pagination status on a focused timeline",
                    )?;
                    (status, stream.boxed())
                }
            };

Here is the definition of .boxed() from the futures_util::stream::StreamExt

    /// Wrap the stream in a Box, pinning it.
    ///
    /// This method is only available when the `std` or `alloc` feature of this
    /// library is activated, and it is activated by default.
    #[cfg(feature = "alloc")]
    fn boxed<'a>(self) -> BoxStream<'a, Self::Item>
    where
        Self: Sized + Send + 'a,
    {
        assert_stream::<Self::Item, _>(Box::pin(self))
    }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would strongly recommend https://docs.rs/n0-future/latest/n0_future/ instead of making our own abstractions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this suggestion, I will update this in the split out PR to use this library for the shim instead.

Copy link
Member

@Hywan Hywan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution!

I've a couple of remarks though.

  • I think it's better to split your single patch into one patch per features: one patch for async_lock, one patch for stream etc. It would help the reviewer,
  • I don't understand why async_lock is necessary at all since tokio::sync is Wasm-compatible (see tokio Wasm support),
  • I suggest to use the #[cfg(target…)] mod sys + pub use sys::* pattern more broadly to reduce most of the #[cfg(target…)] you have; it will also greatly clarify the code!
  • I suggest to use target_family = "wasm" to target Wasm instead of target_arch = "wasm32",
  • I don't understand the need for WasmRuntime because tokio::runtime is Wasm-compatible (see the same link for tokio::sync),
  • I don't understand why you re-implement Next, it is by-design Wasm-compatible. I understand the need to “alias” LocalBoxStream to BoxStream though.

In general, please justify the reason of being of those types in their respective documentation please.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, this. A similar approach would be:

#[cfg(not(target_family = "wasm"))]
mod sys {
    pub use tokio::sync::{};
}

#[cfg(target_family = "wasm"))]
mod sys {
    pub use;
}

pub use sys::*;

I would prefer this choice to be honest.

Also, please use target_family = "wasm" if possible (see target_family) rather than target_arch = "wasm32". wasm64 will come soon, and it will be a maintenance burden.

Comment on lines 148 to 152
#[cfg(target_arch = "wasm32")]
#[derive(Debug)]
/// A dummy guard that does nothing when dropped.
/// This is used for the WASM implementation to match
/// tokio::runtime::EnterGuard.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put the #[…] after the comment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copyright is missing, along with a comment explaining why this is needed, what it does etc.

Comment on lines 138 to 146
#[cfg(not(target_arch = "wasm32"))]
pub type Handle = tokio::runtime::Handle;
#[cfg(not(target_arch = "wasm32"))]
pub type Runtime = tokio::runtime::Runtime;

#[cfg(target_arch = "wasm32")]
pub type Handle = WasmRuntimeHandle;
#[cfg(target_arch = "wasm32")]
pub type Runtime = WasmRuntimeHandle;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could become:

#[cfg(not(target_family = "wasm"))]
mod sys {
    pub use tokio::runtime::{Handle, Runtime};
}

#[cfg(target_family = "wasm")]
mod sys {
    pub struct Handle;
    pub struct Runtime;
}

pub use sys::*;

@maan2003
Copy link
Contributor

I don't understand the need for WasmRuntime because tokio::runtime is Wasm-compatible (see the same link for tokio::sync),

actually no, tokio rt on wasm doesn't integrate with the browser (for fetch() etc), many wasm compatible crates require the wasm-bindgen-futures runtime

Instead of scattered conditional compilation directives throughout the code,
this commit consolidates platform-specific code into sys modules with the pattern:

mod sys {
    // native implementations
}

mod sys {
    // wasm implementations
}

pub use sys::*;

Eliminate the definition of the unnecessary async_lock
@zzorba zzorba force-pushed the wasm32-core-abstractions branch from bde0663 to de2fcc0 Compare May 23, 2025 13:52
@zzorba
Copy link
Contributor Author

zzorba commented May 23, 2025

  1. I will split up patch into two commits (one for Stream, one for Executor, as async_lock is indeed not necessary)
  2. Have adjusted code to use mod sys exports.
  3. Moved to target_family = "wasm"
  4. Tokio rt does not work with wasm, I believe an alternative runtime is necessary as a result.
  5. The issue is the .boxed() call on StreamExt, which has a Send trait. I will try to find a way to add this as an extension for Wasm in a way that doesn't require redefining Stream.

@zzorba
Copy link
Contributor Author

zzorba commented May 23, 2025

Okay, I have split up this PR:

Stream extension: #5085
Executor/JoinHandle extensions: #5088
Runtime: #5089 (dependent on 5088, will mark as ready once 5088 is merged)

@zzorba
Copy link
Contributor Author

zzorba commented May 25, 2025

I believe this PR can be closed in favor of the smaller ones, leaving it open just in case there is any back-and-forth on existing comments.

@Hywan
Copy link
Member

Hywan commented May 26, 2025

Thank you for your work <3!

@zzorba zzorba closed this May 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants