Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions crates/engine/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9406,6 +9406,38 @@ fn streamed_layer_outside_its_zoom_range_reads_nothing() {
assert!(sent.load(std::sync::atomic::Ordering::Relaxed) > before);
}

/// Load All Features on a layer whose web source is still opening waits for it to open, rather
/// than fail with "the layer's source isn't open" (the server here holds its answers a while).
#[test]
fn load_all_features_waits_for_a_source_still_opening() {
use rustgis_data::{Dataset, Feature, Geometry, GeometryType};
let mut ds = Dataset::new("pts", GeometryType::Point, vec![]);
for i in 0..100u32 {
let c = rustgis_geom::Coord::new(f64::from(i) * 0.1, 1.0);
ds.features.push(Feature { id: u64::from(i) + 1, geometry: Some(Geometry::Point(c)), attrs: vec![] });
}
let fgb = rustgis_data::source::fgb::write(&ds).unwrap();
let (base, _) = serve_ranges(fgb.clone());
let mut s = Session::new(Project::default());
s.run("layer.addUrl", &json!({"url": format!("{base}/pts.fgb")})).unwrap();
let text = serde_json::to_string(&s.project).unwrap();
let gate = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let (slow, _) = serve_gated(fgb, true, Some(gate.clone()));
let mut t = Session::new(Project::default());
t.run("project.openText", &json!({"text": text.replace(&base, &slow)})).unwrap();
let layer = t.map().layers.iter().find(|l| l.is_streamed()).unwrap().clone();
// Started as the view starts it, and held by the server: still opening when the load begins.
let mut started = layer.source.clone().unwrap();
started.open().unwrap();
assert!(!started.is_open() && started.is_pending());
let load = std::thread::spawn(move || layer.load_all());
// Still waiting while the server holds its answers, rather than failed at once.
std::thread::sleep(std::time::Duration::from_millis(300));
assert!(!load.is_finished(), "the load didn't wait for the source: {:?}", load.join().unwrap().err());
gate.store(true, std::sync::atomic::Ordering::Relaxed);
assert_eq!(load.join().unwrap().unwrap().features.len(), 100);
}

/// A FlatGeobuf and a GeoParquet by their address are read in place, by range: a view fetches a
/// small part of the file; the project keeps the address and reads it again; asked to, a file is
/// downloaded whole instead.
Expand Down
9 changes: 9 additions & 0 deletions crates/map/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,15 @@ impl Layer {
let mut copy = src.clone();
if !copy.is_open() {
copy.open()?;
// A source by its web address may still be opening in the background (the view or the
// project started it): wait for it, as an export does, rather than read nothing.
copy.wait_open(std::time::Duration::from_secs(30));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait_open sleeps the calling thread for up to 30 s. load_all is reached from tools and from Load All Features. If either runs on the UI thread, a slow or hung server freezes the app for the whole timeout, with no progress shown. Please confirm the callers. If any is on the UI thread, consider a shorter bound, or turning the "still opening" case into a "try again once it has opened" message.

On timeout, the code falls through to read_all. That returns the old "the layer's source isn't open" error, which hides the real cause (the source is slow). A specific message after the wait would be clearer. Confidence: medium.

The new test relies on a 300 ms sleep to release the gate. It should be stable, since the wait is 30 s, but it is timing-dependent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callers: layer.loadAll, the export commands and the tools (gp). Each already reads the whole source synchronously on the thread that runs the command, network reads included, so waiting for the open is part of that same read. The wait is bounded, and the export path (sync_source with block) already waits up to 60 s the same way. On the web build wait_open does not block.

The message is fixed in 53cb50d. If the source still has not opened after the wait, the error is the source's own error, or "the layer's source is still opening: try again once it has opened", instead of "isn't open".

The test no longer relies on a timed release. It checks that the load is still waiting before it opens the gate.

if !copy.is_open() {
return Err(copy
.error
.clone()
.unwrap_or_else(|| "the layer's source is still opening: try again once it has opened".into()));
}
}
let (mut ds, _) = copy.read_all()?;
// A sample isn't all of them: refuse rather than run a tool on part of the data.
Expand Down
Loading