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:
@@ -109,10 +109,51 @@ fn missing_objects_and_async_callers_are_clean_errors() {
|
||||
assert!(ObjectStoreStorage::new(store.clone(), ObjectPath::from("nope.h5")).is_err());
|
||||
put(store.as_ref(), "m.h5", multi_block_file());
|
||||
let storage = ObjectStoreStorage::new(store, ObjectPath::from("m.h5")).unwrap();
|
||||
// From inside a runtime: refused, not a panic or a deadlock.
|
||||
// From inside a current-thread runtime: works (the read runs on the
|
||||
// storage's own runtime), no panic, no deadlock.
|
||||
let rt = tokio_rt();
|
||||
let r = rt.block_on(async { storage.read_at(0, 10).map(|b| b.len()) });
|
||||
assert!(r.unwrap_err().to_string().contains("spawn_blocking"));
|
||||
assert_eq!(r.unwrap(), 10);
|
||||
// Dropping the storage inside a runtime does not panic.
|
||||
rt.block_on(async move { drop(storage) });
|
||||
}
|
||||
|
||||
/// The advice for async callers works: a read in `spawn_blocking` of a
|
||||
/// multi-threaded runtime (where `Handle::try_current` is Ok), and a read
|
||||
/// straight inside a current-thread runtime's task.
|
||||
#[test]
|
||||
fn object_stores_read_from_spawn_blocking_and_inside_runtimes() {
|
||||
let store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
|
||||
let bytes = multi_block_file();
|
||||
put(store.as_ref(), "m.h5", bytes.clone());
|
||||
let want = File::from_bytes(bytes)
|
||||
.unwrap()
|
||||
.dataset("big")
|
||||
.unwrap()
|
||||
.read_f64()
|
||||
.unwrap();
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.build()
|
||||
.unwrap();
|
||||
let (s, w) = (store.clone(), want.clone());
|
||||
let got = rt
|
||||
.block_on(async move {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
assert!(tokio::runtime::Handle::try_current().is_ok());
|
||||
let (f, _) =
|
||||
open_object(s, "m.h5", &Options::default()).map_err(|e| e.to_string())?;
|
||||
let v = f.dataset("big").unwrap().read_f64().unwrap();
|
||||
Ok::<_, String>(v == w)
|
||||
})
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(got, Ok(true));
|
||||
let rt = tokio_rt();
|
||||
let got = rt.block_on(async {
|
||||
let (f, _) = open_object(store, "m.h5", &Options::default()).unwrap();
|
||||
f.dataset("big").unwrap().read_f64().unwrap()
|
||||
});
|
||||
assert_eq!(got, want);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user