Go: a context timeout stops waiting on a Sume job, not the job

context.WithTimeout cancels your status read, never the generation. A 29-line Go loop shows the DeadlineExceeded branch, and why the job id must be stored.

4 min readSume
All posts

A context.WithTimeout around your status calls ends your wait; the Sume job carries on and bills. Sume's docs say it plainly: a client-side timeout does not cancel the job, so you stopped the wait and nothing else. Store the job id before you wait, and on a deadline error log it and poll again later.

Three ways a wait can end

Keep the ending of the wait apart from the ending of the job. Only the last row below changes what Sume does.

How a Sume wait ends and what happens to the job (Sume docs, read 2026-10-07)
Your sideThe jobNext step
Context deadline passesKeeps running and billsPoll again with the stored id
Process restartsKeeps running and billsResume from the stored id
terminal is trueDoneFetch the result if completed
POST /cancelCanceled only if generation has not startedRead the 409 job_generation_already_started otherwise

The loop

The 3-second deadline in the sample is for demonstration; a video job needs minutes, and the docs call 20 minutes a reasonable client deadline for video. select waits for either the poll delay or the deadline, so the loop never sleeps past the context.

package main

import ("context"; "encoding/json"; "errors"; "fmt"; "net/http"; "os"; "time")
func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) // your patience, not the job's
	defer cancel()
	for {
		req, _ := http.NewRequestWithContext(ctx, "GET", os.Getenv("SUME_BASE")+"/v1/jobs/"+os.Args[1]+"/status", nil)
		req.Header.Set("Authorization", "Bearer "+os.Getenv("SUME_API_KEY"))
		res, err := http.DefaultClient.Do(req)
		if errors.Is(err, context.DeadlineExceeded) {
			fmt.Println("stopped waiting; the job keeps running and billing. Store its id.")
			return
		} else if err != nil {
			fmt.Println(err)
			return
		}
		var s struct{ Data struct{ Status string `json:"sume_status"`; Terminal bool } }
		json.NewDecoder(res.Body).Decode(&s)
		res.Body.Close()
		if s.Data.Terminal {
			fmt.Println(s.Data.Status)
			return
		}
		select {
		case <-time.After(2 * time.Second):
		case <-ctx.Done():
		}
	}
}

What it leaves undone

It does not cancel. If you want the job gone, call POST /v1/jobs/{id}/cancel, which succeeds only before generation starts and answers 409 afterwards. Cancelling a job that is already canceled returns the same job.

It also does not read next_poll_after_seconds; the fixed 2-second delay is fine for a demo and wrong for a long queue, where the server's hint is a minimum.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume