| ... | ... | @@ -6,7 +6,7 @@ use crate::fs; |
| 6 | 6 | use crate::{ |
| 7 | 7 | EditStatus, Error, Remote, Replica, Result, |
| 8 | 8 | discover::{Entry, Listed}, |
| 9 | | session::{Section, SyncStatus, reached}, |
| 9 | session::{Observed, Section, SyncStatus, reached, sync_status}, |
| 10 | 10 | worker::Signal, |
| 11 | 11 | }; |
| 12 | 12 | use onestore::Stamp; |
| ... | ... | @@ -112,8 +112,14 @@ struct Watch { |
| 112 | 112 | image: Option<Vec<u8>>, |
| 113 | 113 | /// When the section is next checked. |
| 114 | 114 | due: Instant, |
| 115 | | /// The worker of the session that holds the section. |
| 116 | | held: Option<Weak<Signal>>, |
| 115 | held: Option<Held>, |
| 116 | } |
| 117 | |
| 118 | #[derive(Clone)] |
| 119 | struct Held { |
| 120 | worker: Weak<Signal>, |
| 121 | replica: Weak<Replica>, |
| 122 | observed: Weak<Mutex<Observed>>, |
| 117 | 123 | } |
| 118 | 124 | |
| 119 | 125 | impl Background { |
| ... | ... | @@ -429,7 +435,11 @@ impl Background { |
| 429 | 435 | let reported = watched.reported; |
| 430 | 436 | if let Some(watch) = watched.sections.iter_mut().find(|watch| watch.path == path) { |
| 431 | 437 | worker.watched.store(reported, Ordering::Release); |
| 432 | | watch.held = Some(Arc::downgrade(&worker)); |
| 438 | watch.held = Some(Held { |
| 439 | worker: Arc::downgrade(&worker), |
| 440 | replica: Arc::downgrade(replica), |
| 441 | observed: Arc::downgrade(&section.observed), |
| 442 | }); |
| 433 | 443 | } |
| 434 | 444 | } |
| 435 | 445 | let (shared, path) = (Arc::downgrade(&self.0), path.to_owned()); |
| ... | ... | @@ -449,10 +459,9 @@ impl Background { |
| 449 | 459 | self.0.touched(paths); |
| 450 | 460 | } |
| 451 | 461 | |
| 452 | | /// Each watched section's status as its last check left it, in watch order. A section a |
| 453 | | /// session holds keeps the status it had before. |
| 462 | /// Each watched section's status, in watch order; held sections report their live state. |
| 454 | 463 | pub fn status(&self) -> Vec<(String, SyncStatus)> { |
| 455 | | self.0.watched.lock().map_or_else( |
| 464 | let sections: Vec<_> = self.0.watched.lock().map_or_else( |
| 456 | 465 | |_| Vec::new(), |
| 457 | 466 | |watched| { |
| 458 | 467 | watched |
| ... | ... | @@ -470,11 +479,34 @@ impl Background { |
| 470 | 479 | .map(|error| io::Error::new(error.kind(), error.to_string())), |
| 471 | 480 | queued: status.queued, |
| 472 | 481 | }, |
| 482 | watch.held.clone(), |
| 473 | 483 | ) |
| 474 | 484 | }) |
| 475 | 485 | .collect() |
| 476 | 486 | }, |
| 477 | | ) |
| 487 | ); |
| 488 | sections |
| 489 | .into_iter() |
| 490 | .map(|(path, fallback, held)| { |
| 491 | let live = held.and_then(|held| { |
| 492 | let worker = held.worker.upgrade()?; |
| 493 | let replica = held.replica.upgrade()?; |
| 494 | let observed = held.observed.upgrade()?; |
| 495 | let synced = |
| 496 | Some(worker.synced.load(Ordering::Acquire)).filter(|time| *time != 0); |
| 497 | Some(sync_status(&replica, synced, &observed)) |
| 498 | }); |
| 499 | let status = match live { |
| 500 | Some(Ok(status)) => status, |
| 501 | Some(Err(error)) => SyncStatus { |
| 502 | error: Some(reached(&error)), |
| 503 | ..fallback |
| 504 | }, |
| 505 | None => fallback, |
| 506 | }; |
| 507 | (path, status) |
| 508 | }) |
| 509 | .collect() |
| 478 | 510 | } |
| 479 | 511 | |
| 480 | 512 | /// Sections whose file changed since the last call, by catalog path. |
| ... | ... | @@ -678,7 +710,7 @@ impl Watched { |
| 678 | 710 | for signal in self |
| 679 | 711 | .sections |
| 680 | 712 | .iter() |
| 681 | | .filter_map(|watch| watch.held.as_ref()?.upgrade()) |
| 713 | .filter_map(|watch| watch.held.as_ref()?.worker.upgrade()) |
| 682 | 714 | { |
| 683 | 715 | signal.watched.store(reported, Ordering::Release); |
| 684 | 716 | signal.wake(); |
| ... | ... | @@ -807,7 +839,7 @@ impl Watched { |
| 807 | 839 | if !bound { |
| 808 | 840 | return Next::Connect(self.connection); |
| 809 | 841 | } |
| 810 | | if let Some(worker) = watch.held.as_ref().and_then(Weak::upgrade) { |
| 842 | if let Some(worker) = watch.held.as_ref().and_then(|held| held.worker.upgrade()) { |
| 811 | 843 | worker.wake(); |
| 812 | 844 | watch.due = now + interval; |
| 813 | 845 | continue; |
| ... | ... | @@ -1091,7 +1123,11 @@ mod tests { |
| 1091 | 1123 | let mut watched = Watched::default(); |
| 1092 | 1124 | watched.watch(sections(1, true), now); |
| 1093 | 1125 | let (held, woken) = Signal::new(); |
| 1094 | | watched.sections[0].held = Some(Arc::downgrade(&held)); |
| 1126 | watched.sections[0].held = Some(Held { |
| 1127 | worker: Arc::downgrade(&held), |
| 1128 | replica: Weak::new(), |
| 1129 | observed: Weak::new(), |
| 1130 | }); |
| 1095 | 1131 | assert!(matches!(watched.next(now, false), Next::Connect(0))); |
| 1096 | 1132 | watched.watching(true); |
| 1097 | 1133 | assert!(held.watched.load(Ordering::Acquire)); |
| ... | ... | @@ -1430,6 +1466,116 @@ mod tests { |
| 1430 | 1466 | drop(rewatch); |
| 1431 | 1467 | } |
| 1432 | 1468 | |
| 1469 | #[test] |
| 1470 | fn held_status_tracks_its_worker_without_retaining_the_replica() { |
| 1471 | use crate::session::SyncState; |
| 1472 | use onestore::{ |
| 1473 | PageCreation, |
| 1474 | op::{Edit, Op, SectionOp}, |
| 1475 | }; |
| 1476 | |
| 1477 | let directory = tempfile::tempdir().unwrap(); |
| 1478 | let file = directory.path().join("Held.one"); |
| 1479 | let source = onestore::create_section("Held.one", "", "Author").unwrap(); |
| 1480 | fs::write(&file, &source).unwrap(); |
| 1481 | let (changed, changes) = mpsc::channel(); |
| 1482 | let section = Section::open(&file, directory.path().join("cache"), move || { |
| 1483 | let _ = changed.send(()); |
| 1484 | }) |
| 1485 | .unwrap(); |
| 1486 | changes.recv_timeout(Duration::from_secs(3)).unwrap(); |
| 1487 | section.set_offline(true); |
| 1488 | |
| 1489 | let (stamps, _) = mpsc::channel(); |
| 1490 | let files = Files { |
| 1491 | images: Arc::default(), |
| 1492 | stamps, |
| 1493 | }; |
| 1494 | files.write("Held.one", source); |
| 1495 | let remote = files.clone(); |
| 1496 | let background = Background::start( |
| 1497 | false, |
| 1498 | move |_| { |
| 1499 | let (files, listed) = (remote.clone(), remote.clone()); |
| 1500 | let bind = move |path: &str| File(files.clone(), path.to_owned()); |
| 1501 | let list = move |folder: String| std::future::ready(Ok(listed.list(&folder))); |
| 1502 | Ok(((bind, list), true)) |
| 1503 | }, |
| 1504 | || {}, |
| 1505 | ) |
| 1506 | .unwrap(); |
| 1507 | background.set_offline(true); |
| 1508 | background.watch(vec![files.known("Held.one")]); |
| 1509 | background.hold("Held.one", &section); |
| 1510 | section |
| 1511 | .apply( |
| 1512 | "Author", |
| 1513 | Edit { |
| 1514 | at: crate::now(), |
| 1515 | ops: vec![Op::Section(SectionOp::Create( |
| 1516 | PageCreation::new(None, Some("Queued"), "Author").unwrap(), |
| 1517 | ))], |
| 1518 | }, |
| 1519 | ) |
| 1520 | .unwrap(); |
| 1521 | let pending = section.pending().unwrap(); |
| 1522 | assert_eq!(pending.len(), 1); |
| 1523 | let status = background.status().remove(0).1; |
| 1524 | assert_eq!(status.queued, 1); |
| 1525 | assert!(status.synced.is_some()); |
| 1526 | assert_eq!(status.state(), SyncState::Syncing); |
| 1527 | { |
| 1528 | let watched = background.0.watched.lock().unwrap(); |
| 1529 | assert!(watched.sections[0].status.synced.is_none()); |
| 1530 | assert_eq!(watched.sections[0].status.queued, 0); |
| 1531 | } |
| 1532 | |
| 1533 | let wait = |expected| { |
| 1534 | let deadline = Instant::now() + Duration::from_secs(3); |
| 1535 | loop { |
| 1536 | let status = background.status().remove(0).1; |
| 1537 | if status.state() == expected { |
| 1538 | return status; |
| 1539 | } |
| 1540 | changes |
| 1541 | .recv_timeout(deadline.saturating_duration_since(Instant::now())) |
| 1542 | .unwrap(); |
| 1543 | } |
| 1544 | }; |
| 1545 | section.set_offline(false); |
| 1546 | assert_eq!(wait(SyncState::UpToDate).queued, 0); |
| 1547 | assert!(matches!( |
| 1548 | section.status(pending[0].id).unwrap(), |
| 1549 | Some(EditStatus::Published { .. }) |
| 1550 | )); |
| 1551 | |
| 1552 | let published = fs::read_file(&file).unwrap(); |
| 1553 | fs::remove_file(&file).unwrap(); |
| 1554 | section.wake(); |
| 1555 | assert_eq!( |
| 1556 | wait(SyncState::NotConnected).error.unwrap().kind(), |
| 1557 | io::ErrorKind::NotFound |
| 1558 | ); |
| 1559 | fs::write(&file, published).unwrap(); |
| 1560 | section.wake(); |
| 1561 | assert!(wait(SyncState::UpToDate).error.is_none()); |
| 1562 | assert!( |
| 1563 | background.0.watched.lock().unwrap().sections[0] |
| 1564 | .status |
| 1565 | .synced |
| 1566 | .is_none() |
| 1567 | ); |
| 1568 | |
| 1569 | let replica = Arc::downgrade(section.replica()); |
| 1570 | section.close().unwrap(); |
| 1571 | assert!(replica.upgrade().is_none()); |
| 1572 | assert!( |
| 1573 | background.0.watched.lock().unwrap().sections[0] |
| 1574 | .held |
| 1575 | .is_none() |
| 1576 | ); |
| 1577 | } |
| 1578 | |
| 1433 | 1579 | #[test] |
| 1434 | 1580 | fn a_held_section_is_left_to_its_worker_which_the_watch_wakes() { |
| 1435 | 1581 | let (sender, stamps) = mpsc::channel(); |
| ... | ... | @@ -1462,7 +1608,11 @@ mod tests { |
| 1462 | 1608 | // As `hold` leaves it to a session's worker. |
| 1463 | 1609 | let (signal, woken) = Signal::new(); |
| 1464 | 1610 | if let Ok(mut watched) = background.0.watched.lock() { |
| 1465 | | watched.sections[0].held = Some(Arc::downgrade(&signal)); |
| 1611 | watched.sections[0].held = Some(Held { |
| 1612 | worker: Arc::downgrade(&signal), |
| 1613 | replica: Weak::new(), |
| 1614 | observed: Weak::new(), |
| 1615 | }); |
| 1466 | 1616 | signal.watched.store(watched.reported, Ordering::Release); |
| 1467 | 1617 | } |
| 1468 | 1618 | assert!( |