Context
Context là gì?
context.Context giải quyết vấn đề hủy bỏ và timeout trong hệ thống phân tán. Khi một request HTTP vào server, nó kéo theo nhiều goroutine. Nếu client ngắt kết nối, các goroutine đó cần được dọn dẹp.
// Luôn truyền context làm tham số đầu tiên
func fetchUser(ctx context.Context, id int) (*User, error) {
// Nếu context bị cancel, query sẽ dừng
return db.QueryRowContext(ctx, "SELECT ...", id)
}
Tạo Context
// Root context — điểm xuất phát
ctx := context.Background() // dùng trong main, test
ctx := context.TODO() // placeholder khi chưa biết dùng gì
// Có deadline cứng (thời điểm tuyệt đối)
deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(ctx, deadline)
defer cancel()
// Có timeout (khoảng thời gian tương đối — dùng nhiều hơn)
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() // LUÔN gọi cancel() để giải phóng resource
// Cancel thủ công
ctx, cancel := context.WithCancel(ctx)
go func() {
time.Sleep(3 * time.Second)
cancel() // hủy sau 3 giây
}()
Nhận tín hiệu hủy
func longTask(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err() // context.DeadlineExceeded hoặc context.Canceled
default:
// làm việc...
time.Sleep(100 * time.Millisecond)
}
}
}
Truyền giá trị qua Context
type ctxKey string
const requestIDKey ctxKey = "requestID"
// Set value
ctx = context.WithValue(ctx, requestIDKey, "req-123")
// Get value
if id, ok := ctx.Value(requestIDKey).(string); ok {
fmt.Println("Request ID:", id)
}
// Lưu ý: chỉ dùng WithValue cho metadata (request ID, user ID)
// KHÔNG truyền dependency (db, logger) qua context
example.go
package main
import (
"context"
"fmt"
"time"
)
func slowQuery(ctx context.Context, query string) (string, error) {
ch := make(chan string, 1)
go func() {
time.Sleep(200 * time.Millisecond)
ch <- "Kết quả: " + query
}()
select {
case result := <-ch:
return result, nil
case <-ctx.Done():
return "", fmt.Errorf("query bị hủy: %w", ctx.Err())
}
}
func pipeline(ctx context.Context, steps []string) error {
for i, step := range steps {
select {
case <-ctx.Done():
return fmt.Errorf("pipeline dừng ở bước %d: %w", i, ctx.Err())
default:
}
fmt.Printf("Bước %d: %s\n", i+1, step)
time.Sleep(100 * time.Millisecond)
}
return nil
}
func main() {
// Test 1: timeout đủ
ctx1, cancel1 := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel1()
if result, err := slowQuery(ctx1, "SELECT users"); err != nil {
fmt.Println("❌", err)
} else {
fmt.Println("✓", result)
}
// Test 2: timeout quá ngắn
ctx2, cancel2 := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel2()
if result, err := slowQuery(ctx2, "SELECT orders"); err != nil {
fmt.Println("❌", err)
} else {
fmt.Println("✓", result)
}
// Test 3: cancel pipeline giữa chừng
ctx3, cancel3 := context.WithTimeout(context.Background(), 250*time.Millisecond)
defer cancel3()
steps := []string{"Validate", "Parse", "Transform", "Save", "Notify"}
if err := pipeline(ctx3, steps); err != nil {
fmt.Println("❌ Pipeline:", err)
}
}