-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interrupt reloads when new files change #158
Merged
9999years
merged 1 commit into
main
from
rebeccat/dux-1244-interrupt-reloads-when-new-files-change
Nov 21, 2023
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ use miette::miette; | |
use miette::Context; | ||
use miette::IntoDiagnostic; | ||
use tokio::sync::mpsc; | ||
use tokio::sync::oneshot; | ||
use tokio::sync::Mutex; | ||
use tracing::instrument; | ||
|
||
|
@@ -15,9 +16,10 @@ use crate::shutdown::ShutdownHandle; | |
|
||
use super::Ghci; | ||
use super::GhciOpts; | ||
use super::GhciReloadKind; | ||
|
||
/// An event sent to [`Ghci`]. | ||
#[derive(Debug)] | ||
#[derive(Debug, Clone)] | ||
pub enum GhciEvent { | ||
/// Reload the `ghci` session. | ||
Reload { | ||
|
@@ -26,6 +28,23 @@ pub enum GhciEvent { | |
}, | ||
} | ||
|
||
impl GhciEvent { | ||
/// When we interrupt an event to reload, add the file events together so that we don't lose | ||
/// work. | ||
fn merge(&mut self, other: GhciEvent) { | ||
match (self, other) { | ||
( | ||
GhciEvent::Reload { events }, | ||
GhciEvent::Reload { | ||
events: other_events, | ||
}, | ||
) => { | ||
events.extend(other_events); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Start the [`Ghci`] subsystem. | ||
#[instrument(skip_all, level = "debug")] | ||
pub async fn run_ghci( | ||
|
@@ -51,29 +70,62 @@ pub async fn run_ghci( | |
} | ||
|
||
let ghci = Arc::new(Mutex::new(ghci)); | ||
// The event to respond to. If we interrupt a reload, we may begin the loop with `Some(_)` in | ||
// here. | ||
let mut maybe_event = None; | ||
loop { | ||
// Wait for filesystem events. | ||
let event = tokio::select! { | ||
_ = handle.on_shutdown_requested() => { | ||
ghci.lock().await.stop().await.wrap_err("Failed to quit ghci")?; | ||
break; | ||
} | ||
ret = receiver.recv() => { | ||
ret.ok_or_else(|| miette!("ghci event channel closed"))? | ||
let mut event = match maybe_event.take() { | ||
Some(event) => event, | ||
None => { | ||
// If we don't already have an event to respond to, wait for filesystem events. | ||
let event = tokio::select! { | ||
_ = handle.on_shutdown_requested() => { | ||
ghci.lock().await.stop().await.wrap_err("Failed to quit ghci")?; | ||
break; | ||
} | ||
ret = receiver.recv() => { | ||
ret.ok_or_else(|| miette!("ghci event channel closed"))? | ||
} | ||
}; | ||
tracing::debug!(?event, "Received ghci event from watcher"); | ||
event | ||
} | ||
}; | ||
tracing::debug!(?event, "Received ghci event from watcher"); | ||
|
||
// This channel notifies us what kind of reload is triggered, which we can use to inform | ||
// our decision to interrupt the reload or not. | ||
let (reload_sender, reload_receiver) = oneshot::channel(); | ||
// Dispatch the event. We spawn it into a new task so it can run in parallel to any | ||
// shutdown requests. | ||
let mut task = Box::pin(tokio::task::spawn(dispatch(ghci.clone(), event))); | ||
let mut task = Box::pin(tokio::task::spawn(dispatch( | ||
ghci.clone(), | ||
event.clone(), | ||
reload_sender, | ||
))); | ||
tokio::select! { | ||
_ = handle.on_shutdown_requested() => { | ||
// Cancel any in-progress reloads. This releases the lock so we don't block here. | ||
task.abort(); | ||
ghci.lock().await.stop().await.wrap_err("Failed to quit ghci")?; | ||
break; | ||
} | ||
Some(new_event) = receiver.recv() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth explicitly matching on the |
||
tracing::debug!(?new_event, "Received ghci event from watcher while reloading"); | ||
if should_interrupt(reload_receiver).await { | ||
// Merge the events together so we don't lose progress. | ||
// Then, the next iteration of the loop will pick up the `maybe_event` value | ||
// and respond immediately. | ||
event.merge(new_event); | ||
maybe_event = Some(event); | ||
|
||
// Cancel the in-progress reload. This releases the `ghci` lock to prevent a deadlock. | ||
task.abort(); | ||
|
||
// Send a SIGINT to interrupt the reload. | ||
// NB: This may take a couple seconds to register. | ||
ghci.lock().await.send_sigint().await?; | ||
} | ||
} | ||
ret = &mut task => { | ||
ret.into_diagnostic()??; | ||
tracing::debug!("Finished dispatching ghci event"); | ||
|
@@ -84,12 +136,40 @@ pub async fn run_ghci( | |
Ok(()) | ||
} | ||
|
||
#[instrument(level = "debug", skip(ghci))] | ||
async fn dispatch(ghci: Arc<Mutex<Ghci>>, event: GhciEvent) -> miette::Result<()> { | ||
#[instrument(level = "debug", skip(ghci, reload_sender))] | ||
async fn dispatch( | ||
ghci: Arc<Mutex<Ghci>>, | ||
event: GhciEvent, | ||
reload_sender: oneshot::Sender<GhciReloadKind>, | ||
) -> miette::Result<()> { | ||
match event { | ||
GhciEvent::Reload { events } => { | ||
ghci.lock().await.reload(events).await?; | ||
ghci.lock().await.reload(events, reload_sender).await?; | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Should we interrupt a reload with a new event? | ||
#[instrument(level = "debug", skip_all)] | ||
async fn should_interrupt(reload_receiver: oneshot::Receiver<GhciReloadKind>) -> bool { | ||
let reload_kind = match reload_receiver.await { | ||
Ok(kind) => kind, | ||
Err(err) => { | ||
tracing::debug!("Failed to receive reload kind from ghci: {err}"); | ||
return false; | ||
} | ||
}; | ||
|
||
match reload_kind { | ||
GhciReloadKind::None | GhciReloadKind::Restart => { | ||
// Nothing to do, wait for the task to finish. | ||
tracing::debug!(?reload_kind, "Not interrupting reload"); | ||
false | ||
} | ||
GhciReloadKind::Reload => { | ||
tracing::debug!(?reload_kind, "Interrupting reload"); | ||
true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out that this doesn't just stop
ghci
from inheritingSIGINT
fromghciwatch
, it also stopsghci
from receivingSIGINT
whatsoever.This is a little messier, I guess — when users press Ctrl-C the underlying
ghci
session will see that as well — but thenghciwatch
will shut down, so it doesn't matter much in the end.