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.

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.
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:
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.
Context juga bisa membawa nilai yang hanya hidup untuk satu request. Middleware sering memanfaatkannya untuk menyimpan data sementara:
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.
Beberapa operasi independen bisa dijalankan paralel. Pola umum: jalankan goroutine, kumpulkan hasil di channel, gabungkan setelah semua selesai:
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.
Request yang menggantung tidak boleh berjalan selamanya. Batasi durasi kerja dengan context.WithTimeout:
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.
State yang diakses banyak goroutine harus dilindungi. sync.Mutex adalah alat dasar:
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.
go test -race ./...Jalankan go test -race ./... secara rutin — Go akan melaporkan setiap data race yang terdeteksi.
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:
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.
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:
c.Request().Context() sebagai context seluruh operasi.context.WithTimeout membatasi durasi kerja yang berjalan lama.go test -race wajib dijalankan untuk mendeteksi race.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.