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

Belajar Echo - Context, Timeout & Concurrency

Episode ini membedah concurrency di Echo: context request untuk nilai dan pembatalan, menjalankan goroutine di dalam handler, context timeout untuk membatasi durasi kerja, sinkronisasi state dengan mutex, dan pola aman berbagi data antar request.

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

Pendahuluan

Go terkenal dengan goroutine yang ringan, dan Echo memakai kekuatan itu secara penuh. Tapi concurrency adalah pisau bermata dua: tanpa disiplin, goroutine bocor, data race, dan deadlock mengintai. Episode ini mengajarkan cara memakai concurrency dengan benar di dalam Echo.

Episode 12 ini membedah context request untuk nilai dan pembatalan, menjalankan goroutine di dalam handler, context timeout, sinkronisasi state dengan mutex, dan pola aman berbagi data antar request.

Context Request dan Nilai

Context dari Echo ke Go Standar

Setiap handler Echo punya context yang bisa diakses lewat c.Request().Context(). Context ini dibatalkan saat request selesai atau klien memutus koneksi — sinyal penting bagi operasi yang berjalan lama:

Mengambil context dari request
func handler(c echo.Context) error {
	ctx := c.Request().Context()
	user, err := repo.FindByID(ctx, id)
	if err != nil {
		return err
	}
	return c.JSON(http.StatusOK, user)
}

Selalu teruskan ctx ke fungsi yang menerima context — repository dari episode 9 sudah dirancang untuk itu. Saat klien membatalkan request, operasi database ikut dibatalkan.

Nilai Request-Scoped

Context juga bisa membawa nilai yang hanya hidup untuk satu request. Middleware sering memanfaatkannya untuk menyimpan data sementara:

Menyimpan nilai di context
ctx := context.WithValue(c.Request().Context(), "request_id", reqID)
c.SetRequest(c.Request().WithContext(ctx))

AmbIl kembali nilai tersebut di handler dengan key yang sama. Di v5, gunakan c.Set dan c.Get untuk data yang dibutuhkan middleware Echo seperti nilai dari JWT — akan dibahas di episode 13.

Goroutine di Dalam Handler

Pola Fan-Out yang Aman

Beberapa operasi independen bisa dijalankan paralel. Pola umum: jalankan goroutine, kumpulkan hasil di channel, gabungkan setelah semua selesai:

Fan-out aman dalam handler
type result struct {
	user *model.User
	posts []*model.Post
	err  error
}
 
func handler(c echo.Context) error {
	ctx := c.Request().Context()
	results := make(chan result, 2)
 
	go func() {
		u, err := repo.FindByID(ctx, id)
		results <- result{user: u, err: err}
	}()
	go func() {
		p, err := repo.FindPosts(ctx, id)
		results <- result{posts: p, err: err}
	}()
 
	var user *model.User
	var posts []*model.Post
	for i := 0; i < 2; i++ {
		r := <-results
		if r.err != nil {
			return r.err
		}
		if r.user != nil {
			user = r.user
		}
		if r.posts != nil {
			posts = r.posts
		}
	}
	return c.JSON(http.StatusOK, map[string]interface{}{
		"user":  user,
		"posts": posts,
	})
}

Channel berkapasitas 2 membuat goroutine tidak menunggu receiver. Jangan tulis response dari goroutine — hanya hasilkan nilai dan kembalikan ke handler utama.

Context Timeout untuk Batasi Durasi

Request yang menggantung tidak boleh berjalan selamanya. Batasi durasi kerja dengan context.WithTimeout:

Timeout untuk kerja paralel
ctx, cancel := context.WithTimeout(c.Request().Context(), 5*time.Second)
defer cancel()

Ketika timeout tercapai, context dibatalkan dan semua operasi yang mematuhi context akan mengembalikan error. Ini lapisan pertahanan kedua setelah WriteTimeout dari episode 10.

Sinkronisasi State

Mutex untuk Shared State

State yang diakses banyak goroutine harus dilindungi. sync.Mutex adalah alat dasar:

Counter aman dengan mutex
type Counter struct {
	mu    sync.Mutex
	value int
}
 
func (c *Counter) Inc() {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.value++
}
 
func (c *Counter) Value() int {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.value
}

Setiap akses ke value lewat mu.Lock. Tanpa ini, inkrement bersamaan menghasilkan data race yang bisa dideteksi go test -race.

Deteksi data race
go test -race ./...

Jalankan go test -race ./... secara rutin — Go akan melaporkan setiap data race yang terdeteksi.

Rate Limiting dan Backpressure

Melindungi Resource Bersama

Concurrency tinggi bisa membanjiri database. Kombinasikan rate limiter (episode 6) dengan batasan kerja paralel di dalam service. Untuk operasi yang sangat berat, gunakan pattern worker pool atau semaphore dari golang.org/x/sync:

Semaphore membatasi goroutine
import "golang.org/x/sync/semaphore"
 
sem := semaphore.NewWeighted(10)
for _, item := range items {
	if err := sem.Acquire(ctx, 1); err != nil {
		return err
	}
	go func(item Item) {
		defer sem.Release(1)
		process(item)
	}(item)
}

Semaphore memastikan maksimal 10 goroutine berjalan bersamaan. Ini melindungi database dan resource lain dari ledakan concurrency.

Penutup

Episode 12 membekali kalian disiplin concurrency: c.Request().Context() membawa pembatalan request ke seluruh operasi, goroutine dipakai dengan channel untuk mengumpulkan hasil, context.WithTimeout membatasi durasi, mutex melindungi shared state, dan semaphore membatasi kesibukan.

Inti yang harus dibawa pulang:

  • Gunakan c.Request().Context() sebagai context seluruh operasi.
  • Goroutine tidak boleh menulis response; kembalikan lewat channel.
  • context.WithTimeout membatasi durasi kerja yang berjalan lama.
  • Mutex melindungi shared state dari data race.
  • go test -race wajib dijalankan untuk mendeteksi race.
  • Semaphore membatasi jumlah goroutine paralel.
  • Rate limiter dan backpressure melindungi resource bersama.

Di episode 13 selanjutnya kita akan membahas autentikasi & otorisasi — JWT dengan middleware.JWT, Basic Auth, session cookie, refresh token, middleware RBAC untuk kontrol akses, serta integrasi OAuth2 dan OpenID Connect dengan Keycloak.

Belajar Echo - Context, Timeout & Concurrency | Belajar Echo