Go Context Cancellation Deadlines Timeouts Usage Patterns
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama Go (Golang) untuk melanjutkan topik lainnya.
Kembali ke rujukan utama Go (Golang) untuk melanjutkan topik lainnya.
context.Context adalah carrier untuk batas waktu, sinyal pembatalan, dan nilai request-scoped yang perlu ikut mengalir lewat beberapa layer program. Di Go, context dipakai untuk menjaga pekerjaan tetap relevan dengan request atau job yang sedang berjalan.
Context bukan tempat menyimpan state bisnis utama. Ia dipakai untuk hal lintas lapisan yang sifatnya sementara: request ID, auth metadata, deadline, cancellation signal, dan kontrol lifetime pekerjaan.
Pakai context kalau fungsi atau flow kamu perlu salah satu dari hal berikut:
Aturan praktisnya sederhana: kalau nilainya melekat pada umur request/job, context masuk akal. Kalau nilainya adalah input bisnis, kirimkan sebagai argumen biasa.
Di Go, context biasanya mengikuti pola ini:
context.Background() atau context.TODO() dipakai di titik akarWithCancel, WithTimeout, atau WithDeadlinectx.Done() untuk berhenti cepatctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()Dua hal penting di sini:
cancel() tetap perlu dipanggil, walau timeout akan memutus context otomatis.Tiga konsep ini sering dicampur, padahal bedanya penting.
Cancellation adalah sinyal eksplisit bahwa pekerjaan harus berhenti.
Biasanya terjadi saat:
Listener utamanya adalah <-ctx.Done().
Deadline adalah waktu absolut kapan context dianggap tidak valid lagi.
Contoh:
ctx, cancel := context.WithDeadline(parent, time.Now().Add(3*time.Second))
defer cancel()Deadline cocok kalau kamu ingin menyatakan: “selesaikan sebelum timestamp ini”.
Timeout adalah cara praktis untuk membuat deadline relatif dari sekarang.
ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel()Timeout lebih sering dipakai di aplikasi harian karena lebih mudah dibaca.
Context harus mengalir dari titik awal request sampai ke operasi terendah yang masih perlu kontrol lifetime.
Urutan yang sehat biasanya begini:
ctxctxctx saat I/Octx.Done() untuk berhentiDiagram ini menunjukkan satu prinsip penting: context bukan cuma “dibawa”, tapi juga dipatuhi.
Di HTTP server Go, *http.Request sudah membawa context lewat r.Context().
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// teruskan ctx ke service/repository/client
}ctx := r.Context()func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, err := h.UserService.FindByID(ctx, r.PathValue
r.Context() sudah cukupr.Context(), bukan dari Background()Pola yang sehat adalah menerima context sebagai argumen pertama.
func (s *UserService) FindByID(ctx context.Context, id string) (User, error) {
return s.repo.FindByID(ctx, id)
}
func (r *UserRepo)
Kenapa argumen pertama?
Kalau ada dependency eksternal, pass context sampai ke operasi yang benar-benar bisa block:
Kesalahan yang sering muncul adalah membuat context baru di service tanpa meneruskan parent:
ctx := context.Background()Ini memutus hubungan dengan request aslinya. Akibatnya:
Kalau memang perlu nilai tambahan, gunakan turunan:
ctx = context.WithValue(ctx, key, value)Tapi batasi WithValue untuk metadata lintas layer yang benar-benar perlu, bukan untuk parameter bisnis.
Worker sering hidup lebih lama dari HTTP handler, jadi batas hidupnya harus jelas.
ctx.Done() di loop atau sebelum blocking callfunc worker(ctx context.Context, jobs <-chan Job) {
for {
select {
case <-ctx.Done():
return
case job, ok := <-jobs:
if !
Kalau worker tidak memantau ctx.Done(), dia bisa:
Context paling berguna saat dipakai di boundary I/O.
Contoh umum:
db.QueryContext(ctx, ...)http.NewRequestWithContext(ctx, ...)grpc.ClientConn.Invoke(...) dengan ctxKalau layer atas sudah punya deadline, turunkan context yang sama ke bawah. Jangan bikin timeout berbeda-beda tanpa strategi, karena debugging akan sulit saat request gagal lebih cepat dari yang diduga.
Jangan simpan context.Context di struct sebagai state jangka panjang.
type Service struct {
ctx context.Context // hindari
}Kenapa buruk:
Lebih baik kirim context per method call.
Kalau pakai WithCancel, WithTimeout, atau WithDeadline, panggil cancel().
ctx, cancel := context.WithTimeout(parent, time.Second)
defer cancel()Kenapa penting:
Background() di tengah flowcontext.Background() cocok sebagai akar, bukan untuk memutus lineage request.
Kalau kamu pakai Background() di tengah handler/service, kamu kehilangan cancellation dan deadline dari parent.
WithValue bukan pengganti parameter function.
Gunakan untuk:
Jangan gunakan untuk:
ctx.Err() atau ctx.Done()Context baru berguna kalau code-path memang mematuhinya.
Di loop panjang atau work unit yang bisa berhenti, periksa ctx.Done() atau ctx.Err() supaya stop-nya cepat.
Kalau handler punya timeout 2 detik, lalu service memberi 2 detik lagi, dan repository memberi 2 detik lagi, hasilnya bukan 6 detik aman. Yang terjadi justru batasan jadi sulit diprediksi.
Lebih aman:
func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
context.Context sebagai argumen pertamaWithTimeout / WithDeadline hanya saat ada batas yang jelasdefer cancel() untuk context turunanBackground() untuk memutus request yang sedang berjalanctx.Done()