Timeout & xử lý lỗi — context, select
🧠 Suy nghĩ
"Nếu DB-Server treo mãi thì sao? Không lẽ chờ vô hạn? Phải có hạn chót."
Trong ví dụ, DB-Server cố tình mất 3 giây, nhưng timeout là 2 giây → DB sẽ bị huỷ và
gửi cảnh báo.
📐 Logic
context = "tín hiệu huỷ" — truyền xuyên suốt các goroutine để nói "hết giờ rồi, dừng
lại!".
select = "chờ nhiều sự kiện cùng lúc" — cái nào xảy ra trước thì làm cái đó:
- Sự kiện A: Server phản hồi kịp
- Sự kiện B: Hết thời gian (context bị huỷ)
💻 Code
Trong main():
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Giải phóng tài nguyên context khi main thoát
Sửa hàm checkServerHealth:
func checkServerHealth(ctx context.Context, wg *sync.WaitGroup, s *Server,
results chan<- string, notifier Notifier) {
defer wg.Done()
// Giả lập độ trễ khác nhau
var processingTime time.Duration
if s.Name == "DB-Server" {
processingTime = 3 * time.Second // cố tình chậm
} else {
processingTime = 1 * time.Second
}
select {
case <-time.After(processingTime):
// Trường hợp 1: kịp thời gian
s.SetStatus(true)
results <- fmt.Sprintf("✅ %s (%s) ĐANG HOẠT ĐỘNG.", s.Name, s.IP)
case <-ctx.Done():
// Trường hợp 2: hết giờ
s.SetStatus(false)
// Bọc lỗi: giữ nguyên lỗi gốc + thêm thông tin
err := fmt.Errorf("không thể kết nối %s: %w", s.Name, ctx.Err())
results <- fmt.Sprintf("❌ LỖI: %s (%s) bị treo!", s.Name, s.IP)
notifier.SendAlert(err.Error()) // Đa hình: gọi qua interface
}
}
🔍 Giải thích sâu
context.WithTimeout(...) trả về 2 thứ
ctx— cái để truyền đi cho các goroutinecancel— hàm để huỷ chủ động (bắt buộcdefer cancel()để tránh rò rỉ)
<-time.After(3s) là gì?
time.After(3s) trả về một channel, sau 3 giây sẽ có tín hiệu. <- = "đọc từ channel
này". Nghĩa là "chờ 3 giây".
<-ctx.Done() là gì?
ctx.Done() cũng là channel. Khi timeout hết → channel đóng → <-ctx.Done() chạy ngay.
select { case A: ... case B: ... }
Chờ cả A và B cùng lúc. Cái nào xảy ra trước thì chạy case đó. Cái còn lại bị bỏ qua.
Ví dụ với DB-Server:
- Case A: chờ 3 giây
- Case B: chờ 2 giây (timeout)
- → Case B xảy ra trước → chạy nhánh lỗi ✅
fmt.Errorf("... %w", err) — Wrap error
%w = "gói lỗi gốc bên trong". Sau này có thể dùng errors.Is() để kiểm tra lỗi gốc mà
không mất thông tin.
notifier.SendAlert(...) — Đa hình (polymorphism)
Ở đây notifier có kiểu interface Notifier. Go tự động gọi đúng phương thức của
TelegramBot. Nếu mai truyền SlackBot vào → tự gọi Slack. Đây là sức mạnh của interface.