fix: hotpatching fullstack (#4730)

* fix: hotpatching fullstack

* add hot-patching fullstack test

* add caching to playwright apt-get

* only run playwright on beta

* install binutils?

* dont commit lockfile

* ignore lockfiles

* make sure ld is in path
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-06 16:12:18 -07:00
1 parent a4aef33369
commit bee89b795d
15 files changed
+265 -38

No files matched your search

+2 -3
View File
@@ -715,7 +715,7 @@ impl AppBuilder {
let changed_file = changed_files.first().unwrap();
tracing::info!(
"Hot-patching: {NOTE_STYLE}{}{NOTE_STYLE:#} took {GLOW_STYLE}{:?}ms{GLOW_STYLE:#} ({})",
"Hot-patching: {NOTE_STYLE}{}{NOTE_STYLE:#} took {GLOW_STYLE}{:?}ms{GLOW_STYLE:#}",
changed_file
.display()
.to_string()
@@ -723,8 +723,7 @@ impl AppBuilder {
SystemTime::now()
.duration_since(res.time_start)
.unwrap()
.as_millis(),
triple
.as_millis()
);
self.patches.push(jump_table.clone());
+11 -20
View File
@@ -175,27 +175,18 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Resu
}
BuilderUpdate::BuildReady { bundle } => match bundle.mode {
BuildMode::Thin { ref cache, .. } => {
let elapsed =
bundle.time_end.duration_since(bundle.time_start).unwrap();
match builder.hotpatch(&bundle, id, cache).await {
Ok(jumptable) => {
let pid = match id {
BuildId::CLIENT => builder.client.pid,
_ => builder.server.as_ref().and_then(|s| s.pid),
};
devserver.send_patch(jumptable, elapsed, id, pid).await
}
Err(err) => {
tracing::error!("Failed to hot-patch app: {err}");
if let Err(err) =
builder.hotpatch(&bundle, id, cache, &mut devserver).await
{
tracing::error!("Failed to hot-patch app: {err}");
if let Some(_patching) =
err.downcast_ref::<crate::build::PatchError>()
{
tracing::info!("Starting full rebuild: {err}");
builder.full_rebuild().await;
devserver.send_reload_start().await;
devserver.start_build().await;
}
if let Some(_patching) =
err.downcast_ref::<crate::build::PatchError>()
{
tracing::info!("Starting full rebuild: {err}");
builder.full_rebuild().await;
devserver.send_reload_start().await;
devserver.start_build().await;
}
}
}
+50 -12
View File
@@ -28,7 +28,6 @@ use std::{
sync::Arc,
time::Duration,
};
use subsecond_types::JumpTable;
use syn::spanned::Spanned;
use tokio::process::Command;
@@ -53,7 +52,7 @@ pub(crate) struct AppServer {
pub(crate) watcher_rx: UnboundedReceiver<notify::Event>,
// Tracked state related to open builds and hot reloading
pub(crate) applied_hot_reload_message: HotReloadMsg,
pub(crate) applied_client_hot_reload_message: HotReloadMsg,
pub(crate) file_map: HashMap<PathBuf, CachedFile>,
// Resolved args related to how we go about processing the rebuilds and logging
@@ -185,7 +184,7 @@ impl AppServer {
// Create the runner
let mut runner = Self {
file_map: Default::default(),
applied_hot_reload_message: Default::default(),
applied_client_hot_reload_message: Default::default(),
automatic_rebuilds: true,
watch_fs,
use_hotpatch_engine,
@@ -709,27 +708,66 @@ impl AppServer {
pub(crate) async fn hotpatch(
&mut self,
res: &BuildArtifacts,
bundle: &BuildArtifacts,
id: BuildId,
cache: &HotpatchModuleCache,
) -> Result<JumpTable> {
devserver: &mut WebServer,
) -> Result<()> {
let elapsed = bundle
.time_end
.duration_since(bundle.time_start)
.unwrap_or_default();
let jump_table = match id {
BuildId::CLIENT => self.client.hotpatch(res, cache).await,
BuildId::CLIENT => self.client.hotpatch(bundle, cache).await,
BuildId::SERVER => {
self.server
.as_mut()
.context("Server not found")?
.hotpatch(res, cache)
.hotpatch(bundle, cache)
.await
}
_ => bail!("Invalid build id"),
}?;
if id == BuildId::CLIENT {
self.applied_hot_reload_message.jump_table = self.client.patches.last().cloned();
self.applied_client_hot_reload_message.jump_table = self.client.patches.last().cloned();
}
Ok(jump_table)
// If no server, just send the patch immediately
let Some(server) = self.server.as_mut() else {
devserver
.send_patch(jump_table, elapsed, id, self.client.pid)
.await;
return Ok(());
};
// If we have a server, we need to wait until both the client and server are ready
// Otherwise we end up with an annoying race condition where the client can't actually load the patch
if self.client.stage == BuildStage::Success && server.stage == BuildStage::Success {
let client_jump_table = self
.client
.patches
.last()
.cloned()
.context("Missing client jump table")?;
let server_jump_table = server
.patches
.last()
.cloned()
.context("Missing server jump table")?;
devserver
.send_patch(server_jump_table, elapsed, BuildId::SERVER, server.pid)
.await;
devserver
.send_patch(client_jump_table, elapsed, BuildId::CLIENT, self.client.pid)
.await;
}
Ok(())
}
pub(crate) fn get_build(&self, id: BuildId) -> Option<&AppBuilder> {
@@ -751,7 +789,7 @@ impl AppServer {
/// Get any hot reload changes that have been applied since the last full rebuild
pub(crate) fn applied_hot_reload_changes(&mut self, build: BuildId) -> HotReloadMsg {
let mut msg = self.applied_hot_reload_message.clone();
let mut msg = self.applied_client_hot_reload_message.clone();
if build == BuildId::CLIENT {
msg.jump_table = self.client.patches.last().cloned();
@@ -773,7 +811,7 @@ impl AppServer {
/// Clear the hot reload changes. This should be called any time a new build is starting
pub(crate) fn clear_hot_reload_changes(&mut self) {
self.applied_hot_reload_message = Default::default();
self.applied_client_hot_reload_message = Default::default();
}
pub(crate) fn clear_patches(&mut self) {
@@ -836,7 +874,7 @@ impl AppServer {
/// Store the hot reload changes for any future clients that connect
fn add_hot_reload_message(&mut self, msg: &HotReloadMsg) {
let applied = &mut self.applied_hot_reload_message;
let applied = &mut self.applied_client_hot_reload_message;
// Merge the assets, unknown files, and templates
// We keep the newer change if there is both a old and new change