0
votes

I am trying to send an FFI pointer type to another thread. The struct it points to has been generated by bindgen from opensles-sys

Here is my wrapper struct:

pub struct AndroidAudioIO {
    sl_output_buffer_queue: NonNull<SLObjectItf>,
}
unsafe impl Send for AndroidAudioIO{}

The SLObjectItf type is an alias for *const *const SLObjectItf_ whose definition is generated by bindgen. It's a collection of FFI function pointers.

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct SLObjectItf_ {
    pub Realize: ::std::option::Option<
        unsafe extern "C" fn(self_: SLObjectItf, async: SLboolean) -> SLresult,
    >,
    // More of the same pattern, only extern "C" function pointers, no data
}

I tried adding unsafe impl Send for SLObjectItf_{} and other variants to no avail.

error[E0277]: `std::ptr::NonNull<*const *const opensles::bindings::SLObjectItf_>` cannot be shared between threads safely
  --> src/lib.rs:12:1
   |
12 | / lazy_static! {
13 | | static ref engine:Option<mynoise::Engine<Box<audio::AndroidAudioIO>>> = None;
14 | | }
   | |_^ `std::ptr::NonNull<*const *const opensles::bindings::SLObjectItf_>` cannot be shared between threads safely
   |
   = help: within `audio::AndroidAudioIO`, the trait `std::marker::Sync` is not implemented for `std::ptr::NonNull<*const *const opensles::bindings::SLObjectItf_>`
   = note: required because it appears within the type `audio::AndroidAudioIO`

The reason why I only care about Send but not Sync is that a single thread (the RT audio thread) interacts with this struct, but it is being created on another thread, hence the need to Send the pointer across to the correct thread.

1
Please don't blindy implement Send and/or Sync to any struct. Pointers are not Send for a certain reason. Who is responsible for cleaning or syncing the resource? You could use a Mutex/Arc to gain Send and Sync. - hellow
Since that SDLObjectItf_ is generated, I must then advise you to only implement Send/Sync on a high level API layer. You could also include the reasoning behind implementing Send but not Sync into the question. - E_net4
Please also include the necessary parts to reproduce the issue. In particular, that compiler message refers to an additional portion of the code not shown here. You might be able to make a minimal example that runs in the Playground. - E_net4
I have added the reasoning for Send only, will recreate a repro in Playground when I have access to the code tonight. - Florian Doyon
I only care about Send — but the compiler cares about Sync: the trait `std::marker::Sync` is not implemented - Shepmaster

1 Answers

2
votes

The following code reproduces the same problem (assuming Engine only retains AndroidAudioIO at type-level, so that it can produce such a handler at a later date; it works by direct composition as well).

#[macro_use]
extern crate lazy_static;

use std::marker::PhantomData;
use std::ptr::NonNull;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct SLObjectItf;

pub struct AndroidAudioIO {
    sl_output_buffer_queue: NonNull<SLObjectItf>,
}
unsafe impl Send for AndroidAudioIO {}

#[derive(Debug)]
pub struct Engine<T>(PhantomData<T>);

lazy_static! {
    static ref engine: Option<Engine<AndroidAudioIO>> = None;
}

(Playground)

The issue here is that this Engine entity is in a global static variable, which immediately makes it shared across all threads. This requires Sync, but Engine was not given an implementation of Sync because AudioAndroidIO does not implement Sync. Indeed, regardless of whether the engine contains the audio I/O handler as an attribute or that information only exists at type level, even PhantomData inherits these trait implementations directly from its parameter type. Quoting from the docs:

impl<T: ?Sized> Send for PhantomData<T>
where
    T: Send,
impl<T: ?Sized> Sync for PhantomData<T>
where
    T: Sync

This is likely a case where Engine is OK to have Sync (although PhantomData chooses this safe behaviour of avoiding assumptions about the inner type). To solve this, first make absolute sure that Engine is thread-safe. Then, manually implement Sync for this one.

unsafe impl<T> Sync for Engine<T> {}

I tried adding unsafe impl Send for SLObjectItf_{} and other variants to no avail.

Well, that would have generally been a Bad Idea™ anyway. Implementing Send and/or Sync should be done on top of a safe, high level abstraction of your bindings.