Skip to content

Conversation

@seanybaggins
Copy link

@seanybaggins seanybaggins commented Nov 14, 2025

  • Add Send + Sync bounds to LogProvider trait
  • Add Send + Sync bounds to ExpiryPolicy trait
  • Add Send + Sync bounds to action traits: MetaAction, StaticAction, StatefulAction, HairpinAction, ActionDesc
  • Add Send + Sync bounds to SNAT implementation trait bounds
  • Add unsafe Send + Sync implementations for KStatNamed

Fixes #785: Port should require Send + Sync bounds

This minimal implementation only adds bounds where compilation errors occurred, ensuring Arc<Port> and Arc are Send + Sync.

Before

 cargo clippy
    Checking opte v0.1.0 (/home/sean/personal-repos/opte/lib/opte)
    Checking oxide-vpc v0.1.0 (/home/sean/personal-repos/opte/lib/oxide-vpc)
    Checking xde v0.1.0 (/home/sean/personal-repos/opte/xde)
warning: usage of an `Arc` that is not `Send` and `Sync`
   --> xde/src/xde.rs:403:20
    |
403 |         let ectx = Arc::new(ExecCtx { log: Box::new(opte::KernelLog {}) });
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: `Arc<ExecCtx>` is not `Send` and `Sync` as `ExecCtx` is neither `Send` nor `Sync`
    = help: if the `Arc` will not be used across threads replace it with an `Rc`
    = help: otherwise make `ExecCtx` `Send` and `Sync` or consider a wrapper type such as `Mutex`
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync
    = note: `#[warn(clippy::arc_with_non_send_sync)]` on by default

warning: usage of an `Arc` that is not `Send` and `Sync`
    --> xde/src/xde.rs:2371:8
     |
2371 |     Ok(Arc::new(pb.create(net, limit, limit)?))
     |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     |
     = note: `Arc<Port<VpcNetwork>>` is not `Send` and `Sync` as `Port<VpcNetwork>` is neither `Send` nor `Sync`
     = help: if the `Arc` will not be used across threads replace it with an `Rc`
     = help: otherwise make `Port<VpcNetwork>` `Send` and `Sync` or consider a wrapper type such as `Mutex`
     = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync

warning: `xde` (lib) generated 2 warnings
    Finished `dev` profile [optimized + debuginfo] target(s) in 5.83s

After

cargo clippy
    Checking opte v0.1.0 (/home/sean/personal-repos/opte/lib/opte)
    Checking oxide-vpc v0.1.0 (/home/sean/personal-repos/opte/lib/oxide-vpc)
    Checking xde v0.1.0 (/home/sean/personal-repos/opte/xde)
    Finished `dev` profile [optimized + debuginfo] target(s) in 5.53s

Disclosure

I did use claud-code to assist with this.
Added this function

   fn assert_send_sync<T: Send + Sync>() {}
   assert_send_sync::<Port<crate::engine::NetworkImpl>>();

To identify where Send, Sync needed to be added. Then asked a lot of questions and poked around the code trying to determine safety of

#[cfg(all(not(feature = "std"), not(test)))]
unsafe impl<T: KStatProvider> Send for KStatNamed<T> {}
#[cfg(all(not(feature = "std"), not(test)))]
unsafe impl<T: KStatProvider> Sync for KStatNamed<T> {}

- Add Send + Sync bounds to LogProvider trait
- Add Send + Sync bounds to ExpiryPolicy trait
- Add Send + Sync bounds to action traits: MetaAction, StaticAction,
  StatefulAction, HairpinAction, ActionDesc
- Add Send + Sync bounds to SNAT implementation trait bounds
- Add unsafe Send + Sync implementations for KStatNamed

Fixes oxidecomputer#785: Port<N> should require Send + Sync bounds

This minimal implementation only adds bounds where compilation errors
occurred, ensuring Arc<Port<VpcNetwork>> and Arc<ExecCtx> are Send + Sync.
@FelixMcFelix FelixMcFelix self-requested a review November 18, 2025 09:28
Copy link
Collaborator

@FelixMcFelix FelixMcFelix 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 this, and sorry for the delay in getting back to you. Send/Sync reasoning need close thought, hopefully these comments are helpful.

// 5. No shared mutable state: The raw pointer represents a kernel resource with
// a clear lifecycle (create -> install -> delete) with no Rust-side mutation.
#[cfg(all(not(feature = "std"), not(test)))]
unsafe impl<T: KStatProvider> Send for KStatNamed<T> {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that, currently, this bound is far too broad (i.e., if T is not Send or Sync, neither should KStatNamed be). I believe the correct narrowing might be that we impose, higher in this file, that pub trait KStatProvider: Send + Sync, since the data associated with each kstat will need to be accessed in at least one other kernel thread if there's a kstat invocation.

Comment on lines +310 to +327
// SAFETY: KStatNamed<T> is safe for Send + Sync under the following conditions:
//
// 1. Read-only sharing: After creation, the `ksp` pointer is never mutated - only
// passed to kstat_delete() in Drop. Multiple threads can safely read the same
// immutable pointer value.
//
// 2. Kernel manages concurrency: The kstat framework handles concurrent access to
// the underlying kernel structures via its own internal mechanisms.
//
// 3. Atomic statistics: Individual stats in `vals` use AtomicU64, ensuring each
// counter update is atomic and thread-safe.
//
// 4. Intentional design trade-off: We explicitly do NOT set ks_lock (see struct
// comment), accepting that different threads may see inconsistent snapshots
// of the stats as a group, while individual values remain uncorrupted.
//
// 5. No shared mutable state: The raw pointer represents a kernel resource with
// a clear lifecycle (create -> install -> delete) with no Rust-side mutation.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this can be more concisely summarised, after applying the other suggested change, as a statement that:

  • The underlying T must itself be Send + Sync because it is fully visible to any user of the struct. We meet this through the new type bound on KStatProvider.
  • ksp is itself safe to move between threads, since KSTAT(9S) imposes no MT constraints on callers.
  • ksp is never exposed via a &ref (nor is it used by any methods taking &self), and is only used during drop as you point out above.

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.

Port<N> should require Send + Sync bounds

2 participants