Merge pull request #2848 from Molecule-AI/feat/2799-phase3-pause-1777961500

feat(handlers): migrate Pause loop to StopWorkspaceAuto — #2799 Phase 3 (closes #2799)
This commit is contained in:
Hongming Wang 2026-05-05 07:03:31 +00:00 committed by GitHub
commit d10c1a1a36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 37 additions and 3 deletions

View File

@ -894,6 +894,32 @@ func TestRunRestartCycle_UsesProvisionWorkspaceAutoSync(t *testing.T) {
}
}
// TestPauseHandler_UsesStopWorkspaceAuto — Phase 3 of #2799 source-level
// pin. Pause's per-workspace stop call must route through
// StopWorkspaceAuto so SaaS tenants terminate the EC2 instead of leaking
// it (same drift class as the team-collapse leak #2813 and the
// workspace-delete leak #2814 closed by PR #2824).
//
// Pause-specific bookkeeping (mark paused, clear keys, broadcast)
// stays in the Pause handler — only the "stop the running workload"
// step delegates to the dispatcher. This pin asserts the dispatcher
// is called from the Pause loop with `ws.id`.
func TestPauseHandler_UsesStopWorkspaceAuto(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("getwd: %v", err)
}
src, err := os.ReadFile(filepath.Join(wd, "workspace_restart.go"))
if err != nil {
t.Fatalf("read workspace_restart.go: %v", err)
}
stripped := stripGoComments(src)
if !bytes.Contains(stripped, []byte("h.StopWorkspaceAuto(ctx, ws.id)")) {
t.Errorf("workspace_restart.go must call StopWorkspaceAuto from the Pause loop with `ws.id` — current code does not. " +
"Phase 3 of #2799 migrated this site; do not regress to the inline `if h.provisioner != nil { Stop }` dispatch.")
}
}
// stripGoComments removes // line comments and /* */ block comments
// from Go source. Imperfect (doesn't handle comments-inside-strings)
// but adequate for the source-level pin tests in this file — none of

View File

@ -613,10 +613,18 @@ func (h *WorkspaceHandler) Pause(c *gin.Context) {
}
}
// Stop containers and mark all as paused
// Stop containers and mark all as paused. StopWorkspaceAuto routes
// to whichever backend is wired (CP for SaaS, Docker for self-hosted)
// — pre-2026-05-05 this site inlined `if h.provisioner != nil { Stop }`,
// which silently leaked EC2s on every SaaS Pause (same drift class as
// the team-collapse leak #2813 and the workspace-delete leak #2814,
// both closed by PR #2824). StopWorkspaceAuto returns nil on no-backend
// (no-op), so the Pause-specific bookkeeping (mark paused, clear keys,
// broadcast) still fires regardless of whether anything was actually
// stopped — matches the pre-fix behavior on misconfigured deployments.
for _, ws := range toPause {
if h.provisioner != nil {
h.provisioner.Stop(ctx, ws.id)
if err := h.StopWorkspaceAuto(ctx, ws.id); err != nil {
log.Printf("Pause: stop %s failed: %v — orphan sweeper will reconcile", ws.id, err)
}
db.DB.ExecContext(ctx,
`UPDATE workspaces SET status = $1, url = '', updated_at = now() WHERE id = $2`, models.StatusPaused, ws.id)