Skip to content

NLL non lexical lifetime not handling a (seemingly) simple case #88537

Closed
@rib

Description

@rib

I tried this code:

    async fn connect_peripheral(&self, peripheral_handle: PlatformPeripheralHandle) -> Result<()> {
        let winrt_peripheral = match self.inner.peripherals_by_handle.get(&peripheral_handle) {
            Some(p) => p,
            None => {
                log::error!("Spurious connection request with unknown peripheral handle {:?}", peripheral_handle);
                return Err(Error::Other(anyhow!("Unknown peripheral")));
            }
        };

        let device = BluetoothLEDevice::FromBluetoothAddressAsync(winrt_peripheral.address)
            .map_err(|_| Error::PeripheralUnreachable)?
            .await
            .map_err(|_| Error::PeripheralUnreachable)?;

        // XXX: be careful not to introduce a ref cycle here...
        // (notably we don't pass a winrt session reference to the status handler)
        let platform_bus = self.inner.platform_bus.clone();
        let peripheral_address = winrt_peripheral.address;

        // XXX: I thought non-lexical lifetimes were suppose to fix this kind of issue... :/
        //
        // This scope has been added because otherwise the compiler complains that
        // the connection_status_handler (which is not Send safe) might be used after the
        // final await... the await that is at the end of the function where it should
        // surely be possible for the compiler to recognise that this doesn't need to live
        // that long?
        //
        // Also the handler is moved into device.ConnectionStatusChanged() so I also don't
        // understand why the lifetime isn't seen as ending there?
       // UNCOMMENT brace to fix...
       // {
            // Scope introduced to keep the borrow checker happy
            let connection_status_handler =
                TypedEventHandler::new(move |sender: &Option<BluetoothLEDevice>, _| {
                    if let Some(sender) = sender {
                        match sender.ConnectionStatus() {
                            Ok(BluetoothConnectionStatus::Connected) => {
                                trace!("Peripheral connected: handle={:?}/{}", peripheral_handle, MAC(peripheral_address).to_string());
                                let _ = platform_bus.send(PlatformEvent::PeripheralConnected { peripheral_handle } );
                            },
                            Ok(BluetoothConnectionStatus::Disconnected) => {
                                trace!("Peripheral connected: handle={:?}/{}", peripheral_handle, MAC(peripheral_address).to_string());
                                let _ = platform_bus.send(PlatformEvent::PeripheralDisconnected { peripheral_handle } );
                            },
                            Ok(status) => {
                                log::error!("Spurious bluetooth connection status: {:?}: handle={:?}/{}", status, peripheral_handle, MAC(peripheral_address).to_string());
                            }
                            Err(err) => {
                                log::error!("Failure while querying bluetooth connection status: {:?}: handle={:?}/{}",
                                            err, peripheral_handle, MAC(peripheral_address).to_string())
                            }
                        }
                    }

                    Ok(())
                });
        { // COMMENT out this brace to expand scope around connection_status_handler 
            let mut winrt_peripheral_guard = winrt_peripheral.inner.write().unwrap();
            winrt_peripheral_guard.connection_status_handler = Some(device.ConnectionStatusChanged(connection_status_handler)
                .map_err(|_| Error::Other(anyhow!("Could not add connection status handler")))?);
        }

        self.get_gatt_services(&device).await
    }

I expected this to compile but it only compiles if I expand the scope that covers the locked write near the end so it encompasses connection_status_handler because otherwise the compiler will complain that connection_status_handler is not Send safe and might be used after the final await.

Considering that the await is at the end of the function (so there's no code that could possibly require connection_status_handler to live that long, and also as connection_status_handler is moved into device.ConnectionStatusChanged(connection_status_handler) I don't really understand why the borrow checker isn't recognising that connection_status_handler doesn't need to live beyond the await here?

I tried adding an explicit drop(connection_status_handler); just before the last line and even then the compiler still says it's not dropped until the end of the function scope and gives the same error.

Meta

rustc --version --verbose:

rustc 1.56.0-nightly (5d6804469 2021-08-30)
binary: rustc
commit-hash: 5d6804469d80aaf26f98090ae016af45e267f58f
commit-date: 2021-08-30
host: x86_64-pc-windows-msvc
release: 1.56.0-nightly
LLVM version: 13.0.0
Compiler Error

error: future cannot be sent between threads safely
   --> src\winrt\session.rs:358:99
    |
358 |       async fn connect_peripheral(&self, peripheral_handle: PlatformPeripheralHandle) -> Result<()> {
    |  ___________________________________________________________________________________________________^
359 | |         let winrt_peripheral = match self.inner.peripherals_by_handle.get(&peripheral_handle) {
360 | |             Some(p) => p,
361 | |             None => {
...   |
409 | |         self.get_gatt_services(&device).await
410 | |     }
    | |_____^ future created by async block is not `Send`
    |
    = help: within `impl futures::Future`, the trait `std::marker::Send` is not implemented for `NonNull<c_void>`
note: future is not `Send` as this value is used across an await
   --> src\winrt\session.rs:409:9
    |
378 |         let connection_status_handler =
    |             ------------------------- has type `TypedEventHandler<BluetoothLEDevice, IInspectable>` which is not `Send`
...
409 |         self.get_gatt_services(&device).await
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `connection_status_handler` maybe used later
410 |     }
    |     - `connection_status_handler` is later dropped here
    = note: required for the cast to the object type `dyn futures::Future<Output = std::result::Result<(), Error>> + std::marker::Send`

For reference this is the some of windows-rs generated code for the TypedEventHandler struct:

Generated code

        #[repr(transparent)]
        #[derive(
            :: std :: cmp :: PartialEq,
            :: std :: cmp :: Eq,
            :: std :: clone :: Clone,
            :: std :: fmt :: Debug,
        )]
        pub struct TypedEventHandler<TSender, TResult>(
            ::windows::IUnknown,
            ::std::marker::PhantomData<TSender>,
            ::std::marker::PhantomData<TResult>,
        )
        where
            TSender: ::windows::RuntimeType + 'static,
            TResult: ::windows::RuntimeType + 'static;
        impl<
                TSender: ::windows::RuntimeType + 'static,
                TResult: ::windows::RuntimeType + 'static,
            > TypedEventHandler<TSender, TResult>
        {
            pub fn new<
                F: FnMut(
                        &<TSender as ::windows::Abi>::DefaultType,
                        &<TResult as ::windows::Abi>::DefaultType,
                    ) -> ::windows::Result<()>
                    + 'static,
            >(
                invoke: F,
            ) -> Self {
                let com = TypedEventHandler_box::<TSender, TResult, F> {
                    vtable: &TypedEventHandler_box::<TSender, TResult, F>::VTABLE,
                    count: ::windows::RefCount::new(1),
                    invoke,
                };
                unsafe { std::mem::transmute(::std::boxed::Box::new(com)) }
            }
            pub fn Invoke<'a>(
                &self,
                sender: impl ::windows::IntoParam<'a, TSender>,
                args: impl ::windows::IntoParam<'a, TResult>,
            ) -> ::windows::Result<()> {
                let this = self;
                unsafe {
                    (::windows::Interface::vtable(this).3)(
                        ::windows::Abi::abi(this),
                        sender.into_param().abi(),
                        args.into_param().abi(),
                    )
                    .ok()
                }
            }
        }

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions