Belajar Gin - Context, Timeout & Concurrency
Series/Belajar Gin/Episode 12
Episode 12 of 23

Belajar Gin - Context, Timeout & Concurrency

Episode ini membedah konteks dan konkurensi di Gin: memakai c.Request.Context untuk pembatalan, c.Copy untuk goroutine, context timeout di handler, rate limiting sederhana, serta sinkronisasi akses data dengan mutex.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Go dikenal dengan konkurensi yang murah, dan Gin adalah framework yang dibangun di atasnya. Episode 12 ini membedah context, timeout & concurrency: bagaimana request membawa context yang bisa dibatalkan, bagaimana menjalankan pekerjaan di goroutine dengan aman, bagaimana memberi batas waktu eksekusi, dan bagaimana melindungi data bersama dari akses konkuren.

Pemahaman di sini krusial karena bug konkurensi adalah kelas bug paling sulit dilacak. Deadlock, data race, dan goroutine yang bocor sering muncul di aplikasi yang menangani ribuan request per detik. Episode ini membangun kebiasaan yang mencegahnya sejak awal.

Context Request dan Pembatalan

Meneruskan c.Request.Context

Setiap request membawa context yang dibatalkan ketika klien menutup koneksi. Teruskan context ini ke semua panggilan blocking:

Meneruskan context request
func getUserHandler(c *gin.Context) {
    user, err := h.svc.GetUser(c.Request.Context(), id)
    if err != nil {
        c.Error(err)
        return
    }
    c.JSON(200, user)
}

c.Request.Context() mengembalikan context request yang otomatis dibatalkan saat klien memutus koneksi atau timeout. Jika diteruskan ke query database (pola WithContext di episode 9), query akan berhenti begitu request dibatalkan — menghemat resource database.

c.Copy untuk Goroutine

Kenapa Membutuhkan Salinan

Context Gin adalah objek mutable yang tidak aman dipakai bersamaan di beberapa goroutine. Jika kalian perlu menjalankan pekerjaan asinkron dari handler, buat salinan dengan c.Copy():

Memakai c.Copy di goroutine
func sendEmailHandler(c *gin.Context) {
    email := c.PostForm("email")
 
    cp := c.Copy()
    go func() {
        time.Sleep(2 * time.Second)
        logger.Info("email dikirim", "to", cp.PostForm("email"))
        notifyDone(cp.Request.Context())
    }()
 
    c.JSON(202, gin.H{"status": "diproses"})
}

c.Copy() mengembalikan salinan context yang aman dipakai di luar siklus handler. Handler tetap bisa langsung merespons 202 Accepted sementara pekerjaan asinkron berjalan di latar belakang. Hindari membaca field context asli c di dalam goroutine tanpa salinan.

Prinsip Pekerjaan Asinkron

Untuk pekerjaan yang lebih berat — kirim email, proses file, kirim notifikasi — jangan mengikat request. Kirim ke channel atau antrian:

Channel pekerjaan
var jobs = make(chan Job, 100)
 
func worker() {
    for job := range jobs {
        process(job)
    }
}
 
func enqueueHandler(c *gin.Context) {
    var job Job
    if err := c.ShouldBindJSON(&job); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    jobs <- job
    c.JSON(202, gin.H{"accepted": true})
}

jobs <- job mengirim pekerjaan ke channel berkapasitas 100. Worker mengonsumsi dan memprosesnya di luar request. Ini pola antrian paling sederhana; untuk production, pakai message broker seperti Redis Streams atau Kafka.

Context Timeout di Handler

Membatasi Durasi Eksekusi

Beberapa operasi — integrasi pihak ketiga, aggregasi banyak sumber — bisa berjalan terlalu lama. Batasi dengan context.WithTimeout:

Timeout di handler
ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
defer cancel()
 
orders, err := h.svc.AggregateOrders(ctx, userID)
if err != nil {
    c.Error(fmt.Errorf("aggregasi: %w", err))
    return
}
c.JSON(200, gin.H{"orders": orders})

context.WithTimeout(c.Request.Context(), 3*time.Second) membuat context anak yang otomatis dibatalkan setelah 3 detik. defer cancel() memastikan resource context dibebaskan saat handler selesai — walaupun deadline tercapai. Batas waktu yang masuk akal mencegah satu request lambat menggantung goroutine selamanya.

Rate Limiting Sederhana

Token Bucket per IP

Proteksi API dari lonjakan request bisa dimulai dari limiter sederhana memakai golang.org/x/time/rate:

Install rate limiter
go get golang.org/x/time/rate
Limiter per IP
var (
    mu      sync.Mutex
    buckets = make(map[string]*rate.Limiter)
)
 
func getLimiter(ip string) *rate.Limiter {
    mu.Lock()
    defer mu.Unlock()
    lim, ok := buckets[ip]
    if !ok {
        lim = rate.NewLimiter(rate.Every(time.Second), 20)
        buckets[ip] = lim
    }
    return lim
}
 
func rateLimit() gin.HandlerFunc {
    return func(c *gin.Context) {
        if !getLimiter(c.ClientIP()).Allow() {
            c.AbortWithStatusJSON(429, gin.H{"error": "terlalu banyak request"})
            return
        }
        c.Next()
    }
}

rate.NewLimiter(rate.Every(time.Second), 20) membolehkan rata-rata 20 request per detik dengan burst 20. Mutex melindungi map buckets karena diakses banyak goroutine. Perhatikan juga peta per-IP sebaiknya dibersihkan berkala agar tidak membesar — atau pakai limiter global di episode 14 untuk versi yang lebih matang.

Sinkronisasi Akses Data

Mutex untuk Data Bersama

Saat banyak goroutine memodifikasi data yang sama, gunakan sync.Mutex:

Counter aman konkurensi
type VisitCounter struct {
    mu     sync.Mutex
    visits int
}
 
func (v *VisitCounter) Inc() {
    v.mu.Lock()
    defer v.mu.Unlock()
    v.visits++
}

v.mu.Lock() melindungi v.visits dari data race. Tanpa mutex, dua goroutine bisa membaca dan menulis nilai yang sama secara bersamaan, menghasilkan angka yang salah. Untuk operasi sederhana seperti ini, sync/atomic bisa lebih cepat, tetapi mutex lebih mudah dipahami untuk logika kompleks.

Penutup

Inti yang harus dibawa pulang:

  • c.Request.Context() menyebarkan pembatalan ke seluruh panggilan blocking.
  • c.Copy() dibutuhkan saat memakai context di goroutine.
  • context.WithTimeout membatasi durasi eksekusi handler.
  • errors.Is(err, context.DeadlineExceeded) untuk merespons timeout dengan 504.
  • rate.Limiter dari golang.org/x/time memberikan proteksi lonjakan request.
  • sync.Mutex mencegah data race pada data bersama.

Di episode 13 selanjutnya kita akan membedah autentikasi & otentikasi session — BasicAuth, JWT dengan golang-jwt, alur access token dan refresh token, session cookie, serta penyimpanan dan revocation token di Redis.

Belajar Gin - Context, Timeout & Concurrency | Belajar Gin