research: teardown per-topic container on publish + delete (P1)
Path B spawned a per-topic team container on start_topic (research runtime) but nothing ever stopped it. Containers accumulated on gw-04 across the lifetime of every topic — the earlier cleanup pass reclaimed 6.72 GB of these. Now the container is stopped + removed automatically at the two terminal transitions: - delete_topic — the topic row is gone, the container is meaningless. - approve_publish (reviewing → publishing) — the research work is done. Artifact writing (queued as R1) reads from the durable run_events log, so it doesn't need a live runtime. Wiring is a fire-and-forget teardown() helper in research_container.rs: - Connects Docker via the same socket-proxy shim as spawn(). - Calls the existing stop() which is idempotent (404 = already gone, 304 = already stopped, both treated as ok). - Errors log with eprintln! and don't block the API response — a topic is done whether Docker is reachable or not. Dev machines without Docker running just log a debug line and return. Path A (repo-only mount + shared clawmates_core network) is untouched so there's no Traefik dynamic-file cleanup needed. If we ever add per-topic Traefik routing, extend teardown() with the corresponding file remove. Follow-up: gw-04 will still have any legacy containers from before this commit. One-shot `docker ps -a --filter "name=research-" -q | xargs -r docker rm -f` on gw-04 is the manual sweep.
This commit is contained in:
@@ -242,3 +242,27 @@ pub async fn stop(docker: &Docker, name: &str) -> Result<(), String> {
|
|||||||
Err(e) => Err(format!("remove {name}: {e}")),
|
Err(e) => Err(format!("remove {name}: {e}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fire-and-forget teardown for a topic's runtime — called from
|
||||||
|
/// `approve_publish` (topic reaches terminal `publishing` state) and
|
||||||
|
/// `delete_topic`. Non-fatal: Docker unreachable or container already
|
||||||
|
/// gone both log a debug line and return so the API response stays
|
||||||
|
/// clean. Callers should NOT `await?` on this — the API contract is
|
||||||
|
/// "the topic is done" whether Docker is reachable or not.
|
||||||
|
pub async fn teardown(topic_id: Uuid) {
|
||||||
|
let name = container_name_for(topic_id);
|
||||||
|
let docker = match connect() {
|
||||||
|
Ok(d) => d,
|
||||||
|
Err(e) => {
|
||||||
|
// Dev-machine no-docker path — silently no-op. Prod always
|
||||||
|
// has the socket-proxy sidecar, so this branch is a signal
|
||||||
|
// rather than a warning.
|
||||||
|
eprintln!("research_container::teardown({topic_id}): docker connect failed: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match stop(&docker, &name).await {
|
||||||
|
Ok(_) => eprintln!("research_container::teardown({topic_id}): removed {name}"),
|
||||||
|
Err(e) => eprintln!("research_container::teardown({topic_id}): stop {name} failed: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -543,6 +543,10 @@ pub async fn delete_topic(
|
|||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, ApiError> {
|
) -> Result<StatusCode, ApiError> {
|
||||||
cm_db::repo::research_topics::delete(&state.pool, id, user.workspace_id.as_uuid()).await?;
|
cm_db::repo::research_topics::delete(&state.pool, id, user.workspace_id.as_uuid()).await?;
|
||||||
|
// Teardown the per-topic team container (Path B). Fire-and-forget:
|
||||||
|
// the topic row is already gone, so any Docker failure is a log-line
|
||||||
|
// problem, not an API-response problem.
|
||||||
|
crate::research_container::teardown(id).await;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -885,6 +889,10 @@ async fn decide_publish(
|
|||||||
"publishing",
|
"publishing",
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
// The research work is done — tear down the per-topic team
|
||||||
|
// container. Artifact-writing (R1) doesn't need a live runtime;
|
||||||
|
// it reads run_events from the durable log.
|
||||||
|
crate::research_container::teardown(approval.topic_id).await;
|
||||||
}
|
}
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user