clawhdf5-remote: ObjectStoreStorage works from any thread
It refused whenever Handle::try_current() was Ok, which is also the case inside spawn_blocking threads — so the workaround its own error message recommended failed the same way, and the backend could only be used from a bare std::thread in a tokio application. Reads are now spawned on the storage's own runtime and the caller waits on a channel: the future never runs on the caller's thread, so neither a spawn_blocking thread nor a current-thread runtime can deadlock or panic (a read inside a runtime blocks that thread, like any blocking call; the docs still recommend spawn_blocking there). Tests: a read in spawn_blocking of a multi-thread runtime and a read inside a current-thread runtime's task give File::open's values (both errors before). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
@@ -3,12 +3,11 @@
|
||||
//!
|
||||
//! `object_store` is async and [`Storage`] is synchronous (parsing is CPU
|
||||
//! work; `docs/design/range-reads.md` §3 (a)). The storage owns a small
|
||||
//! multi-threaded tokio runtime (two worker threads) and blocks the calling
|
||||
//! thread on it for each read, so it can be used from ordinary threads —
|
||||
//! several at once. Calling it from inside another tokio runtime would
|
||||
//! block that runtime's worker, so it refuses with
|
||||
//! [`RemoteError::Usage`]: from async code, read in
|
||||
//! `tokio::task::spawn_blocking`.
|
||||
//! multi-threaded tokio runtime (two worker threads): each read is spawned
|
||||
//! on it and the calling thread waits for the result, so it can be used
|
||||
//! from any thread — several at once, and from async code too. From async
|
||||
//! code prefer `tokio::task::spawn_blocking` (a read blocks the thread it
|
||||
//! is called on, which inside a runtime is one of its workers).
|
||||
//!
|
||||
//! The object is pinned when the storage is made: its size, and its ETag
|
||||
//! (sent as `If-Match` with every read, and compared with every response)
|
||||
@@ -105,19 +104,23 @@ impl ObjectStoreStorage {
|
||||
)
|
||||
}
|
||||
|
||||
fn block_on<T>(
|
||||
/// Run `fut` on the storage's own runtime and wait for it. The future
|
||||
/// never runs on the caller's thread, so the caller's context does not
|
||||
/// matter: a plain thread, `spawn_blocking`, or even inside another
|
||||
/// runtime (whose thread is then blocked for the duration of the read,
|
||||
/// as by any blocking call, but nothing deadlocks or panics).
|
||||
fn block_on<T: Send + 'static>(
|
||||
&self,
|
||||
fut: impl std::future::Future<Output = Result<T, RemoteError>>,
|
||||
fut: impl std::future::Future<Output = Result<T, RemoteError>> + Send + 'static,
|
||||
) -> Result<T, RemoteError> {
|
||||
if tokio::runtime::Handle::try_current().is_ok() {
|
||||
return Err(RemoteError::Usage(
|
||||
"ObjectStoreStorage blocks on its own runtime and cannot be read from inside \
|
||||
an async runtime; read in tokio::task::spawn_blocking"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
let rt = self.runtime.as_ref().expect("runtime lives until drop");
|
||||
rt.block_on(fut)
|
||||
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
||||
rt.spawn(async move {
|
||||
let _ = tx.send(fut.await);
|
||||
});
|
||||
rx.recv().map_err(|_| {
|
||||
RemoteError::ObjectStore("the object store task ended without a result".into())
|
||||
})?
|
||||
}
|
||||
|
||||
fn options(&self, range: Range<u64>) -> GetOptions {
|
||||
|
||||
Reference in New Issue
Block a user