| ... | @@ -1016,9 +1016,12 @@ impl Library { | ... | @@ -1016,9 +1016,12 @@ impl Library { |
| 1016 | | 1016 | |
| 1017 | /// Closes the sections kept open, before the notebook's files change. | 1017 | /// Closes the sections kept open, before the notebook's files change. |
| 1018 | pub fn close_kept(&self) { | 1018 | pub fn close_kept(&self) { |
| 1019 | if let Ok(mut kept) = self.kept.lock() { | 1019 | let sections = self |
| 1020 | close(kept.trim(0, |_| 1)); | 1020 | .kept |
| 1021 | } | 1021 | .lock() |
| | 1022 | .map(|mut kept| kept.trim(0, |_| 1)) |
| | 1023 | .unwrap_or_default(); |
| | 1024 | close(sections); |
| 1022 | } | 1025 | } |
| 1023 | | 1026 | |
| 1024 | pub async fn close_kept_async(&self) -> Result<(), Box<dyn Error>> { | 1027 | pub async fn close_kept_async(&self) -> Result<(), Box<dyn Error>> { |
| ... | @@ -1598,6 +1601,103 @@ pub fn locate(path: &Path) -> Located { | ... | @@ -1598,6 +1601,103 @@ pub fn locate(path: &Path) -> Located { |
| 1598 | mod tests { | 1601 | mod tests { |
| 1599 | use super::*; | 1602 | use super::*; |
| 1600 | | 1603 | |
| | 1604 | #[test] |
| | 1605 | fn closing_a_stalled_section_leaves_the_kept_cache_accessible() { |
| | 1606 | use std::sync::{Condvar, mpsc}; |
| | 1607 | |
| | 1608 | struct Stalled { |
| | 1609 | file: PathBuf, |
| | 1610 | entered: Arc<Mutex<Option<mpsc::Sender<()>>>>, |
| | 1611 | release: Arc<(Mutex<bool>, Condvar)>, |
| | 1612 | } |
| | 1613 | |
| | 1614 | impl notebook::Remote for Stalled { |
| | 1615 | fn read(&mut self) -> io::Result<Vec<u8>> { |
| | 1616 | onestore::read_file(&self.file) |
| | 1617 | } |
| | 1618 | |
| | 1619 | fn stamp(&mut self) -> io::Result<onestore::Stamp> { |
| | 1620 | if let Some(entered) = self.entered.lock().unwrap().take() { |
| | 1621 | entered.send(()).unwrap(); |
| | 1622 | let (lock, changed) = &*self.release; |
| | 1623 | let _ = changed |
| | 1624 | .wait_timeout_while(lock.lock().unwrap(), Duration::from_secs(5), |done| { |
| | 1625 | !*done |
| | 1626 | }) |
| | 1627 | .unwrap(); |
| | 1628 | } |
| | 1629 | Err(io::ErrorKind::TimedOut.into()) |
| | 1630 | } |
| | 1631 | |
| | 1632 | fn publish( |
| | 1633 | &mut self, |
| | 1634 | transaction: &onestore::Transaction, |
| | 1635 | ) -> Result<(), onestore::CommitError> { |
| | 1636 | transaction.commit_file(&self.file) |
| | 1637 | } |
| | 1638 | |
| | 1639 | fn confirm(&mut self, base: &onestore::Stamp) -> Result<(), onestore::CommitError> { |
| | 1640 | onestore::confirm_file(&self.file, base) |
| | 1641 | } |
| | 1642 | } |
| | 1643 | |
| | 1644 | let root = |
| | 1645 | std::env::temp_dir().join(format!("snowbound-stalled-close-{}", std::process::id())); |
| | 1646 | let _ = notebook::fs::remove_dir_all(&root); |
| | 1647 | notebook::fs::create_dir_all(&root).unwrap(); |
| | 1648 | let file = root.join("Stalled.one"); |
| | 1649 | notebook::fs::write( |
| | 1650 | &file, |
| | 1651 | &onestore::create_section("Stalled.one", "", "Author").unwrap(), |
| | 1652 | ) |
| | 1653 | .unwrap(); |
| | 1654 | let (entered, waiting) = mpsc::channel(); |
| | 1655 | let release = Arc::new((Mutex::new(false), Condvar::new())); |
| | 1656 | let gate = Arc::clone(&release); |
| | 1657 | let entered = Arc::new(Mutex::new(Some(entered))); |
| | 1658 | let section = Section::open_with( |
| | 1659 | &file, |
| | 1660 | root.join("cache"), |
| | 1661 | move |file| { |
| | 1662 | Ok(Stalled { |
| | 1663 | file: file.to_owned(), |
| | 1664 | entered: Arc::clone(&entered), |
| | 1665 | release: Arc::clone(&gate), |
| | 1666 | }) |
| | 1667 | }, |
| | 1668 | || {}, |
| | 1669 | ) |
| | 1670 | .unwrap(); |
| | 1671 | waiting.recv_timeout(Duration::from_secs(3)).unwrap(); |
| | 1672 | let library = Arc::new(Library::section(&file, &root.join("cache"))); |
| | 1673 | library.keep("Stalled.one", section); |
| | 1674 | let closing = Arc::clone(&library); |
| | 1675 | let close = std::thread::spawn(move || closing.close_kept()); |
| | 1676 | let deadline = Instant::now() + Duration::from_secs(1); |
| | 1677 | let available = loop { |
| | 1678 | if library |
| | 1679 | .kept |
| | 1680 | .try_lock() |
| | 1681 | .is_ok_and(|kept| !kept.contains(&"Stalled.one".to_owned())) |
| | 1682 | { |
| | 1683 | break true; |
| | 1684 | } |
| | 1685 | if Instant::now() >= deadline { |
| | 1686 | break false; |
| | 1687 | } |
| | 1688 | std::thread::yield_now(); |
| | 1689 | }; |
| | 1690 | *release.0.lock().unwrap() = true; |
| | 1691 | release.1.notify_all(); |
| | 1692 | close.join().unwrap(); |
| | 1693 | assert!( |
| | 1694 | available, |
| | 1695 | "stopping a network worker held the UI's cache lock" |
| | 1696 | ); |
| | 1697 | drop(library); |
| | 1698 | notebook::fs::remove_dir_all(root).unwrap(); |
| | 1699 | } |
| | 1700 | |
| 1601 | #[cfg(unix)] | 1701 | #[cfg(unix)] |
| 1602 | #[test] | 1702 | #[test] |
| 1603 | fn stalled_theme_reads_leave_the_cache_usable_and_keep_local_edits() { | 1703 | fn stalled_theme_reads_leave_the_cache_usable_and_keep_local_edits() { |