ElyMark: renders asked of a failed page end at once (M-08)
The tally remembers why the page went down; until a load succeeds, render() ends each new request with that reason instead of calling a dead page. end_renders is a free function with a gpui test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2c19d027a6
commit
22d2d04d1e
8 files changed
+98
-35
No files matched your search
@@ -201,8 +201,6 @@ fn clear(dir: &Path) -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// What tab `id` restores: by file, or untitled by number.
|
||||
|
||||
impl Desk {
|
||||
/// A tab just shown takes its kept mode, place, text.
|
||||
pub fn restore_shown(&mut self, id: u64, cx: &mut Context<Self>) {
|
||||
|
||||
+43
-10
@@ -11,11 +11,13 @@ pub struct Tally<R> {
|
||||
reported: u64,
|
||||
renders: HashMap<u64, R>,
|
||||
asked: u64,
|
||||
/// Why the page is down, until it loads again.
|
||||
down: Option<String>,
|
||||
}
|
||||
|
||||
impl<R> Tally<R> {
|
||||
pub fn new() -> Self {
|
||||
Tally { load: 0, changed: 0, reported: 0, renders: HashMap::new(), asked: 0 }
|
||||
Tally { load: 0, changed: 0, reported: 0, renders: HashMap::new(), asked: 0, down: None }
|
||||
}
|
||||
|
||||
pub fn load(&self) -> u64 {
|
||||
@@ -38,8 +40,19 @@ impl<R> Tally<R> {
|
||||
self.lapse()
|
||||
}
|
||||
|
||||
/// The renders owed, as the page fails or restarts.
|
||||
pub fn lapse(&mut self) -> Vec<R> {
|
||||
/// A page loaded: it answers again.
|
||||
pub fn loaded(&mut self) -> Vec<R> {
|
||||
self.down = None;
|
||||
self.restart()
|
||||
}
|
||||
|
||||
/// The page failed: owed and later renders end with `why`.
|
||||
pub fn fail(&mut self, why: String) -> Vec<R> {
|
||||
self.down = Some(why);
|
||||
self.lapse()
|
||||
}
|
||||
|
||||
fn lapse(&mut self) -> Vec<R> {
|
||||
self.renders.drain().map(|(_, render)| render).collect()
|
||||
}
|
||||
|
||||
@@ -62,10 +75,13 @@ impl<R> Tally<R> {
|
||||
}
|
||||
|
||||
/// Owes `render`; its number goes to the page.
|
||||
pub fn ask(&mut self, render: R) -> u64 {
|
||||
pub fn ask(&mut self, render: R) -> Result<u64, (R, String)> {
|
||||
if let Some(why) = &self.down {
|
||||
return Err((render, why.clone()));
|
||||
}
|
||||
self.asked += 1;
|
||||
self.renders.insert(self.asked, render);
|
||||
self.asked
|
||||
Ok(self.asked)
|
||||
}
|
||||
|
||||
pub fn answered(&mut self, ask: u64) -> Option<R> {
|
||||
@@ -99,12 +115,29 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_restart_ends_every_owed_render() {
|
||||
fn a_load_or_failure_ends_every_owed_render() {
|
||||
let mut tally = Tally::new();
|
||||
let ask = tally.ask("export");
|
||||
assert_eq!(tally.restart(), vec!["export"]);
|
||||
// Its late answer finds nobody waiting.
|
||||
assert_eq!(tally.answered(ask), None);
|
||||
let (html, text) = (tally.ask("html").expect("up"), tally.ask("text").expect("up"));
|
||||
let mut owed = tally.loaded();
|
||||
owed.sort();
|
||||
assert_eq!(owed, ["html", "text"]);
|
||||
// Late answers find nobody waiting.
|
||||
assert_eq!((tally.answered(html), tally.answered(text)), (None, None));
|
||||
let pdf = tally.ask("pdf").expect("up");
|
||||
assert_eq!(tally.fail("denied".into()), ["pdf"]);
|
||||
assert_eq!(tally.answered(pdf), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_page_refuses_renders_until_it_loads() {
|
||||
let mut tally = Tally::new();
|
||||
tally.fail("denied".into());
|
||||
assert_eq!(tally.ask("html"), Err(("html", "denied".to_string())));
|
||||
// A refresh is no load: the page is still down.
|
||||
assert!(tally.restart().is_empty());
|
||||
assert_eq!(tally.ask("text"), Err(("text", "denied".to_string())));
|
||||
assert!(tally.loaded().is_empty());
|
||||
assert!(tally.ask("pdf").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+29
-22
@@ -37,6 +37,20 @@ pub const LARGE: usize = 2 * 1024 * 1024;
|
||||
/// A render's answer: the page's reply, or why not.
|
||||
type Rendered = Box<dyn FnOnce(Result<Value, String>, &mut gpui::App)>;
|
||||
|
||||
/// Renders the page will never answer end with `why`.
|
||||
fn end_renders(pending: Vec<Rendered>, why: &str, cx: &mut gpui::App) {
|
||||
if pending.is_empty() {
|
||||
return;
|
||||
}
|
||||
log::warn!("mark: {} renders end: {why}", pending.len());
|
||||
let why = why.to_string();
|
||||
cx.defer(move |cx| {
|
||||
for done in pending {
|
||||
done(Err(why.clone()), cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The page failed; the shell shows why.
|
||||
pub struct Failed(pub String);
|
||||
|
||||
@@ -186,7 +200,7 @@ impl MarkView {
|
||||
self.saved = mark(&text);
|
||||
// Reports and renders from before belong to the old text.
|
||||
let lapsed = self.tally.restart();
|
||||
self.end_renders(lapsed, "the text was replaced from disk", cx);
|
||||
end_renders(lapsed, "the text was replaced from disk", cx);
|
||||
crate::page::refresh(self.web.read(cx), &text, self.tally.load());
|
||||
cx.emit(Reported);
|
||||
}
|
||||
@@ -209,9 +223,13 @@ impl MarkView {
|
||||
done: impl FnOnce(Result<Value, String>, &mut gpui::App) + 'static,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let ask = self.tally.ask(Box::new(done));
|
||||
let ask = serde_json::json!({ "kind": kind, "ask": ask });
|
||||
crate::page::call(self.web.read(cx), "render", &ask);
|
||||
match self.tally.ask(Box::new(done)) {
|
||||
Ok(ask) => {
|
||||
let ask = serde_json::json!({ "kind": kind, "ask": ask });
|
||||
crate::page::call(self.web.read(cx), "render", &ask);
|
||||
}
|
||||
Err((done, why)) => end_renders(vec![done], &why, cx),
|
||||
}
|
||||
}
|
||||
|
||||
/// `text` replaces the editor's, as one undoable edit.
|
||||
@@ -257,8 +275,8 @@ impl MarkView {
|
||||
match event {
|
||||
// A reload shows the last text that arrived.
|
||||
Event::Loaded => {
|
||||
let lapsed = self.tally.restart();
|
||||
self.end_renders(lapsed, "the page reloaded", cx);
|
||||
let lapsed = self.tally.loaded();
|
||||
end_renders(lapsed, "the page reloaded", cx);
|
||||
self.shown = false;
|
||||
let text = self.doc.lock().expect("the text lock").current.clone();
|
||||
let at = (self.mode, self.tally.load(), self.place);
|
||||
@@ -268,8 +286,8 @@ impl MarkView {
|
||||
Event::Query(said, reply) => self.query(said, reply, cx),
|
||||
Event::Leave(url) => self.follow(url, cx),
|
||||
Event::Failed(why) => {
|
||||
let lapsed = self.tally.lapse();
|
||||
self.end_renders(lapsed, why, cx);
|
||||
let lapsed = self.tally.fail(why.clone());
|
||||
end_renders(lapsed, why, cx);
|
||||
cx.emit(Failed(why.clone()));
|
||||
}
|
||||
Event::Printed(path, _) => {
|
||||
@@ -278,20 +296,6 @@ impl MarkView {
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the old page will never answer end with `why`.
|
||||
fn end_renders(&mut self, pending: Vec<Rendered>, why: &str, cx: &mut Context<Self>) {
|
||||
if pending.is_empty() {
|
||||
return;
|
||||
}
|
||||
log::warn!("mark: {} renders lost with the page", pending.len());
|
||||
let why = why.to_string();
|
||||
cx.defer(move |cx| {
|
||||
for done in pending {
|
||||
done(Err(why.clone()), cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// A clicked link: web and mail open outside.
|
||||
fn follow(&mut self, url: &str, cx: &mut Context<Self>) {
|
||||
if ely_office::outward(url) {
|
||||
@@ -486,3 +490,6 @@ impl Render for MarkView {
|
||||
div().size_full().on_drop(drop).child(self.web.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,18 @@
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
#[gpui::test]
|
||||
fn each_owed_render_ends_once_with_the_reason(cx: &mut gpui::TestAppContext) {
|
||||
let got = Rc::new(RefCell::new(Vec::<(u8, Result<Value, String>)>::new()));
|
||||
let pending = (0..2)
|
||||
.map(|n| {
|
||||
let got = got.clone();
|
||||
Box::new(move |rendered, _: &mut gpui::App| got.borrow_mut().push((n, rendered))) as super::Rendered
|
||||
})
|
||||
.collect();
|
||||
cx.update(|cx| super::end_renders(pending, "denied", cx));
|
||||
cx.run_until_parked();
|
||||
let denied = || Err("denied".to_string());
|
||||
assert_eq!(*got.borrow(), [(0, denied()), (1, denied())]);
|
||||
}
|
||||
Reference in new issue
Block a user