Testing concurrent code with testing/synctest

Bài tóm tắt Go 2025 3. Quy mô lớn Độ khó: Trung bình

Go · 2025 · 3. Quy mô lớn · Độ khó: Trung bình — bài gốc

Bài của đội ngôn ngữ Go giới thiệu một package thử nghiệm trong Go 1.24 (bật bằng GOEXPERIMENT=synctest, chưa thuộc cam kết tương thích). Ghi lại vì nó đưa ý "mô phỏng tất định" vào runtime của ngôn ngữ.

Bối cảnh

  • Test code đồng thời thường phải chứng minh một điều không xảy ra (hàm chưa được gọi trước khi context huỷ). Cách hay dùng là ngủ một khoảng rồi kiểm: 10 ms thì chậm khi cộng dồn nhiều test, mà vẫn flaky vì CI dùng chung có thể khựng vài giây. Theo bài, chỉ chọn được nhanh hoặc tin cậy, không được cả hai.
  • Test code dùng thời gian thật thì chậm và flaky; dùng đồng hồ giả thì phải viết code nhận clock tuỳ chọn và tránh hàm của package time.

Thay đổi chính

  • Package chỉ có hai hàm. synctest.Run chạy một hàm trong goroutine mới; nó và mọi goroutine nó sinh ra nằm trong một bubble. synctest.Wait chờ tới khi mọi goroutine trong bubble đều bị chặn bởi goroutine khác trong bubble.
  • Trong bubble, package time chạy trên đồng hồ giả: thời gian chỉ tiến khi mọi goroutine đều bị chặn bền (durably blocked); khi đó Wait trả về nếu đang có người chờ, nếu không thì thời gian nhảy tới mốc gần nhất có thể đánh thức một goroutine, và nếu không còn mốc nào thì bubble deadlock và Run panic.
  • "Chặn bền" chỉ gồm các thao tác chỉ goroutine trong bubble mới gỡ được: kênh tạo trong bubble, select toàn nhánh chặn bền, time.Sleep, sync.Cond.Wait, sync.WaitGroup.Wait. Mutex không tính (có thể bị giữ bởi goroutine ngoài bubble, như cache toàn cục trong reflect); I/O mạng thật không tính, nên test client/server dùng mạng trong bộ nhớ (net.Pipe). Thao tác trên kênh của bubble từ bên ngoài thì panic.
  • Race detector hiểu Wait: thiếu một lần Wait thì báo data race đúng chỗ. Ví dụ trong bài: test hành vi Expect: 100-continue của net/http tất định, không cần sửa code được test.

Vì sao

  • Mục tiêu theo bài: test code đồng thời đơn giản, nhanh và tin cậy mà không bắt code production phải thiết kế quanh clock giả.

Bài học cho SWE

  • Flaky do thời gian là vấn đề của môi trường chạy, không chỉ của từng test: một runtime điều khiển được thời gian và biết khi nào mọi thứ đã đứng yên thì loại được cả lớp lỗi.
  • Ranh giới của mô phỏng phải nói rõ thứ gì nằm ngoài (mutex dùng chung, I/O thật); ở đó vẫn phải thay bằng bản giả.

Liên quan