rust 为`时雄::sync::RwLockWriteGuard`实现`downgrade_map`

w6lpcovy  于 2023-04-12  发布在  其他
关注(0)|答案(1)|浏览(105)

我目前正在tokio::sync::RwLockWriteGuard上实现一个扩展方法downgrade_map(+ try_downgrade_map),它从RwLockWriteGuard<'a, T>RwLockReadGuard<'a, U>,并带有一个f: impl FnOnce(&T) -> &U

pub fn downgrade_map<'a, T: ?Sized, U: ?Sized>(
    guard: tokio::sync::RwLockWriteGuard<'a, T>,
    f: impl FnOnce(&T) -> &U,
) -> tokio::sync::RwLockReadGuard<'a, U> {
    let value = f(&*guard) as *const U;
    let guard = tokio::sync::RwLockWriteGuard::downgrade(guard);
    // SAFETY: We only allow `&T -> &U`, not `&mut T -> &U`,
    //         which would allow breaking interior mutability,
    //         (e.g. `std::cell::Cell::get_mut`)
    tokio::sync::RwLockReadGuard::map(guard, |_| unsafe { &*value })
}

this tokio issue中,使用签名为&mut T -> &mut U的函数存在问题,并且在降级后会导致问题。
然而,在这里,我们只允许&T -> &U。这是否解决了可靠性问题?或者有任何其他考虑因素使这个函数不可靠?
编辑:在最小化示例时,我不小心把它带到了一个不再需要unsafe的地方。我最初试图实现的try_downgrade_map确实需要unsafe:

fn try_downgrade_map<'a, T, U>(
    guard: tokio::sync::RwLockWriteGuard<'a, T>,
    f: impl FnOnce(&T) -> Option<&U>,
) -> Result<tokio::sync::RwLockReadGuard<'a, U>, tokio::sync::RwLockWriteGuard<'a, T>> {
    let value = match f(&*this) {
        Some(value) => value as *const U,
        None => return Err(this),
    };
    let guard = tokio::sync::RwLockWriteGuard::downgrade(this);
    let guard = tokio::sync::RwLockReadGuard::map(guard, |_| unsafe { &*value });
    Ok(guard)
}

这不能在安全代码中实现,因为我想确保降级只发生在f返回None的情况下(我不能调用f两次)。我使用它的原始代码如下所示:

pub async fn foo(lock: RwLock<T>) {
    // Check with a read lock
    if let Ok(value) = RwLockReadGuard::try_map(lock.read().await, |value| value.try_get()) {
        return value;
    }

    // Else "upgrade" and check if it was changed in the meantime
    let guard = lock.write().await;
    if let Ok(value) = RwLockWriteGuard::try_downgrade_map(guard, |value| value.try_get()) {
        return value;
    }

    // Use `guard` mutably
    // ...
}
5ssjco0h

5ssjco0h1#

问题中的代码应该是合理的,但是这些方法的版本已经被添加到tokio 1.27.0this PR中。
现在你可以直接使用它们,问题中的foo函数就可以按原样工作了。

相关问题