Hiểu rõ bản chất quy tắc thay vì tìm cách lách luật trong lập trình hệ thống
Bài viết phân tích một sai lầm phổ biến trong phát triển driver Windows, nơi các lập trình viên tuân thủ đúng "văn bản" của hướng dẫn nhưng lại vi phạm tinh thần cốt lõi, dẫn đến treo hệ thống. Nó nhấn mạnh tầm quan trọng của việc hiểu rõ tại sao các quy tắc tồn tại thay vì chỉ làm theo danh sách kiểm tra một cách máy móc.

Trong tài liệu hướng dẫn về các phương pháp tốt nhất (best practices) dành cho các hàm callback liên quan đến quy trình (process) và luồng (thread) của Windows, Microsoft đưa ra một danh sách các điều cấm khá nghiêm ngặt. Các lập trình viên được yêu cầu giữ cho các quy trình này ngắn gọn, không gọi dịch vụ chế độ người dùng (user mode service), không truy cập Registry, và quan trọng nhất là không thực hiện các cuộc gọi chặn (blocking) hoặc giao tiếp liên tiến trình (IPC).
Tại sao các quy tắc này lại khắt khe đến vậy? Các hàm callback này được kích hoạt trong các sự kiện cấp thấp của hệ thống như khi một tiến trình bắt đầu hoặc kết thúc, khi một DLL được tải hay giải phóng. Nếu bạn mất quá nhiều thời gian để xử lý chúng, toàn bộ hệ thống sẽ bị chậm đi. Những yêu cầu cực đoan như "Không gọi Registry" cho thấy rằng các hàm này có thể được gọi ngay cả khi hệ thống đang giữ các khóa nội bộ (internal locks).
Tài liệu cũng gợi ý sử dụng System Worker Threads để xếp hàng công việc, đặc biệt là các tác vụ chậm hoặc có hành vi chặn. Đây là một lời khuyên hợp lý để chuyển các công việc tốn kém ra khỏi hàm callback, giúp hàm chính trả về kết quả nhanh chóng.
Tuy nhiên, các kỹ sư hỗ trợ doanh nghiệp thường xuyên gặp phải các trường hợp hệ thống bị treo do driver vi phạm nguyên tắc "phải trả về nhanh chóng". Một mô hình sai lầm phổ biến (anti-pattern) là driver trong hàm callback của mình đã làm đúng hướng dẫn: xếp hàng công việc cho System Worker Thread. Nhưng ngay sau đó, họ lại chặn (block) hàm callback đó cho đến khi công việc trong Worker Thread hoàn tất.
Đây là một ví dụ điển hình của việc tuân thủ quy tắc mà không hiểu lý do đằng sau nó.
Quy tắc cốt lõi là hàm callback cần nhanh và trả về ngay lập tức. Driver đó đã tuân thủ "văn bản" của luật bằng cách ủy quyền công việc cho System Worker Thread, và không có quy tắc nào nói rõ là "Đừng đợi công việc hoàn tất". Do đó, họ nghĩ rằng mình đã tìm được một kẽ hở để thực thi các công việc chạy dài đồng bộ.
Nhưng các quy tắc "Không thực hiện cuộc gọi chặn" và "Không đồng bộ hóa với các luồng khác vì có thể dẫn đến bế tắc (deadlock)" đã chỉ rõ rằng bạn không nên chặn trong callback của mình trong thời gian dài. Các điều "Không" chỉ đang nêu ra những cách phổ biến khiến callback của bạn bị chặn.
May mắn thay, tài liệu đã được cập nhật vào năm 2020 để nhấn mạnh trường hợp cụ thể này: "Nếu bạn sử dụng System Worker Threads, đừng đợi công việc hoàn tất. Việc làm như vậy sẽ làm mất đi mục đích của việc xếp hàng công việc để hoàn thành không đồng bộ."
Một người có thể lập luận rằng quy tắc này đã được bao phủ bởi quy tắc "Không đồng bộ hóa với các luồng khác", nhưng có vẻ như nhà sản xuất driver đã diễn giải nó theo kiểu: "Nhưng tôi không đồng bộ hóa với một luồng khác. Tôi đang đồng bộ hóa trên một sự kiện (event)!" Tuy nhiên, sự kiện đó được thiết lập bởi một luồng khác, nên về hiệu quả, bạn vẫn đang đồng bộ hóa với một luồng khác.
Đồng nghiệp của tôi trong nhóm hỗ trợ mô tả trường hợp này là cái cớ "Không phải tôi, là em trai tôi". Bạn bị bố mẹ cấm không được bật TV, nên bạn bảo em trai mình bật hộ. Về mặt kỹ thuật, bạn không chạm vào nút bật TV, nhưng về hiệu quả, bạn vẫn bật TV vì em trai hành động theo chỉ dẫn của bạn. (Đây là lý do tại sao các hợp đồng thường chứa các điều khoản như "không được tiết lộ hoặc gây ra việc tiết lộ", để bạn không thể nói "Không, tôi hoàn toàn không tiết lộ. Tôi đưa thông tin cho Bob, và là Bob đã tiết lộ nó!").
Tài liệu hướng dẫn lẽ ra nên mở đầu bằng một tuyên ngôn rõ ràng như sau:
"Hàm callback phải thực hiện công việc của mình một cách nhanh chóng mà không gây chặn. Nếu bạn cần thực hiện công việc phức tạp hoặc đồng bộ hóa với các luồng/quy trình khác, hãy thực hiện công việc đó không đồng bộ, ví dụ như sử dụng System Worker Threads."
Sau đó, nó mới liệt kê các ví dụ về những hành vi được tính là gây chặn. Việc hiểu rõ "tại sao" sẽ giúp các nhà phát triển tránh được những cạm bẫy nguy hiểm khi làm việc với nhân hệ điều hành.



