@@ -119,7 +119,7 @@ func (h *Handlers) actionsRunDelete(w http.ResponseWriter, r *http.Request) { |
| 119 | 119 | return |
| 120 | 120 | } |
| 121 | 121 | if h.d.ObjectStore != nil && len(objectKeys) > 0 { |
| 122 | | - go h.purgeArtifactObjects(objectKeys) |
| 122 | + go h.purgeArtifactObjects(context.WithoutCancel(r.Context()), objectKeys) |
| 123 | 123 | } |
| 124 | 124 | w.WriteHeader(http.StatusNoContent) |
| 125 | 125 | } |
@@ -128,8 +128,12 @@ func (h *Handlers) actionsRunDelete(w http.ResponseWriter, r *http.Request) { |
| 128 | 128 | // request lifecycle. Failures are logged but never surfaced — the |
| 129 | 129 | // authoritative DB row is gone, and the cleanup sweeper retries |
| 130 | 130 | // orphan-object deletion on its own schedule. |
| 131 | | -func (h *Handlers) purgeArtifactObjects(keys []string) { |
| 132 | | - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
| 131 | +// |
| 132 | +// parent is expected to be `context.WithoutCancel(r.Context())` so |
| 133 | +// the goroutine survives response completion while still carrying |
| 134 | +// trace/log values from the originating request. |
| 135 | +func (h *Handlers) purgeArtifactObjects(parent context.Context, keys []string) { |
| 136 | + ctx, cancel := context.WithTimeout(parent, 30*time.Second) |
| 133 | 137 | defer cancel() |
| 134 | 138 | for _, k := range keys { |
| 135 | 139 | if err := h.d.ObjectStore.Delete(ctx, k); err != nil { |
@@ -247,7 +251,7 @@ func (h *Handlers) actionsArtifactDelete(w http.ResponseWriter, r *http.Request) |
| 247 | 251 | return |
| 248 | 252 | } |
| 249 | 253 | if h.d.ObjectStore != nil { |
| 250 | | - go h.purgeArtifactObjects([]string{artifact.ObjectKey}) |
| 254 | + go h.purgeArtifactObjects(context.WithoutCancel(r.Context()), []string{artifact.ObjectKey}) |
| 251 | 255 | } |
| 252 | 256 | w.WriteHeader(http.StatusNoContent) |
| 253 | 257 | } |