我目前正在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
// ...
}
1条答案
按热度按时间5ssjco0h1#
问题中的代码应该是合理的,但是这些方法的版本已经被添加到
tokio 1.27.0
的this PR中。现在你可以直接使用它们,问题中的
foo
函数就可以按原样工作了。