inotify giúp ứng dụng Linux nhận sự kiện khi file hoặc thư mục thay đổi, nhưng nó không phải nhật ký bất biến và không bảo đảm ứng dụng thấy mọi thay đổi. Một file watcher production phải tự quản lý watch đệ quy, ghép sự kiện rename, xử lý atomic save, phát hiện queue overflow và có đường rescan để khôi phục trạng thái đúng.
Bài viết dựa trên inotify(7), inotify_init1(2) và inotify_add_watch(2). Mục tiêu là thiết kế watcher cho đồng bộ cache, build system, indexer hoặc agent cấu hình—không dùng inotify như nguồn audit duy nhất.
1. Mental model: watch inode, nhận tên tương đối
Mỗi inotify instance là một file descriptor có hàng đợi sự kiện và danh sách watch. inotify_add_watch() trả về watch descriptor (wd) nhận diện filesystem object trong instance đó. Khi watch một directory, event cho con trực tiếp mang name tương đối với directory; kernel không trả sẵn đường dẫn tuyệt đối.
Ứng dụng vì thế cần map wd → directory path/identity. Watch gắn với object/inode chứ không phải một chuỗi pathname vĩnh viễn: rename, hard link, unmount và delete có thể khiến cách nhìn bằng path thay đổi. Cache path phải được cập nhật theo sự kiện và xác minh lại khi nghi ngờ.
2. Khởi tạo an toàn và tích hợp event loop
int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (fd == -1) {
perror("inotify_init1");
exit(EXIT_FAILURE);
}
IN_NONBLOCK cho phép drain đến EAGAIN mà không treo event loop; IN_CLOEXEC tránh rò file descriptor sang process sau exec. Có thể đăng ký fd này với epoll, poll hoặc event loop của runtime. Khi readable, đọc theo batch cho đến EAGAIN; không chỉ đọc một event rồi quay về tác vụ nặng.
Tách consumer nhanh khỏi processor chậm bằng queue nội bộ có giới hạn. Consumer parse, chuẩn hóa và enqueue; worker xử lý nội dung. Nếu queue ứng dụng đầy, phải backpressure hoặc chuyển sang reconcile—không được drop im lặng.
3. Parse buffer đúng vì event có độ dài biến đổi
char buf[64 * 1024]
__attribute__((aligned(__alignof__(struct inotify_event))));
for (;;) {
ssize_t n = read(fd, buf, sizeof buf);
if (n == -1 && errno == EAGAIN) break;
if (n == -1) { perror("read"); break; }
for (char *p = buf; p < buf + n; ) {
struct inotify_event *ev = (struct inotify_event *) p;
handle_event(ev);
p += sizeof(*ev) + ev->len;
}
}
len bao gồm vùng name và padding. Không tăng pointer bằng strlen(name), không giả định mỗi read() chỉ trả một event và không giữ pointer vào buffer sau khi buffer được dùng lại. Sao chép dữ liệu cần thiết vào object sở hữu riêng.
4. Chọn mask theo semantics, không dùng IN_ALL_EVENTS theo thói quen
| Sự kiện | Ý nghĩa thường dùng | Lưu ý |
|---|---|---|
IN_CREATE | Entry mới xuất hiện trong directory | Nếu là directory, cần thêm watch đệ quy |
IN_CLOSE_WRITE | File mở để ghi vừa được đóng | Thường hữu ích hơn xử lý mọi IN_MODIFY |
IN_MOVED_FROM/TO | Entry được đổi tên hoặc di chuyển | Ghép bằng cookie |
IN_DELETE | Entry con bị xóa | Cập nhật index/cache |
IN_DELETE_SELF | Object đang watch bị xóa | Watch sắp bị loại |
IN_MOVE_SELF | Object đang watch bị di chuyển | Path cache có thể sai |
IN_IGNORED | Watch đã bị xóa tự động/thủ công | Xóa mapping wd đúng generation |
IN_Q_OVERFLOW | Kernel đã làm mất sự kiện | wd=-1; bắt buộc reconcile |
Chọn ít sự kiện hơn giảm tải queue và logic. Nếu mục tiêu là xử lý file hoàn chỉnh, IN_CLOSE_WRITE thường ít nhiễu hơn IN_MODIFY, nhưng không thay thế rename-based publish. Thiết kế semantics từ hành vi producer thật.
5. inotify không recursive: phải watch từng directory
Watch một directory chỉ theo dõi directory đó và các entry con trực tiếp, không tự bao phủ toàn bộ cây. Thuật toán bootstrap an toàn hơn:
- Tạo inotify instance và bật consumer ngay.
- Thêm watch cho root trước khi enumerate con.
- Duyệt từng directory, thêm watch trước khi đi sâu vào nội dung của nó.
- Buffer event phát sinh trong lúc scan.
- Đối chiếu snapshot với event đã buffer, rồi chạy một pass reconcile.
Khi nhận IN_CREATE|IN_ISDIR hoặc IN_MOVED_TO|IN_ISDIR, thêm watch cho directory mới và scan cây con của nó. Trong khoảng từ lúc event xảy ra đến lúc watch được cài, nội dung bên trong có thể đã đổi; scan sau khi add watch giúp lấp khoảng trống, nhưng vẫn cần logic deduplicate.
6. Rename là cặp sự kiện không nhất thiết đứng cạnh nhau
Rename trong phạm vi được quan sát thường tạo IN_MOVED_FROM và IN_MOVED_TO có cùng cookie. Hai event có thể bị xen bởi event khác hoặc nằm ở hai lần đọc; đừng chỉ nhìn event kế tiếp.
pending_moves[cookie] = {
.old_parent = parent_for(wd),
.old_name = copy(name),
.seen_at = monotonic_now()
};
/* Khi gặp MOVED_TO cùng cookie: ghép và cập nhật path subtree.
Khi timeout: coi MOVED_FROM là đi ra ngoài cây. */
Di chuyển từ ngoài vào cây chỉ có phía MOVED_TO; di chuyển ra ngoài chỉ có MOVED_FROM. Queue overflow có thể làm mất một nửa cặp. Cache correctness không được phụ thuộc vào việc luôn ghép được cookie—khi thiếu, hãy scan lại vùng liên quan hoặc toàn root.
7. Atomic save khiến watch trực tiếp file dễ gãy
Nhiều editor và công cụ cấu hình không ghi đè file tại chỗ. Chúng ghi file tạm, fsync, rồi rename thay file đích. Watch đặt trực tiếp lên inode cũ có thể nhận IN_DELETE_SELF/IN_IGNORED và không còn theo dõi nội dung mới.
Với file cấu hình quan trọng, thường nên watch directory cha và lọc theo tên. Xử lý cả close-write tại chỗ lẫn move-to/replace. Sau event, mở lại bằng path theo policy an toàn, kiểm tra metadata và đọc phiên bản mới; không giữ fd cũ rồi giả định nó trỏ tới file vừa publish.
8. IN_Q_OVERFLOW là mất tính đúng đắn, không chỉ là metric
Hàng đợi kernel hữu hạn. Khi producer tạo event nhanh hơn consumer drain, kernel phát IN_Q_OVERFLOW với wd=-1; các event đã mất không thể suy ra chính xác. Cách phục hồi:
- Đánh dấu cache
dirtyvà tăng counter overflow. - Tiếp tục drain queue để tránh nghẽn thêm.
- Chặn publication của state chưa được xác minh hoặc gắn generation.
- Quét lại toàn bộ scope, dựng snapshot/watch map mới.
- Đối chiếu thay đổi phát sinh trong lúc rescan.
- Chỉ đánh dấu healthy sau khi reconcile hoàn tất.
Tăng kernel limit chỉ làm overflow khó xảy ra hơn; nó không thay thế recovery. Process pause, burst lớn hoặc consumer bug vẫn có thể vượt mọi queue hữu hạn.
9. Event có thể coalesced và không phải audit trail
Các event giống nhau liên tiếp có thể được coalesce khi chưa đọc. Số event không bằng số syscall hoặc số lần người dùng sửa file. inotify cũng không cung cấp lịch sử bền vững qua restart và không chứa actor identity đầy đủ.
Nếu yêu cầu là audit bảo mật, dùng audit subsystem hoặc giải pháp được thiết kế cho provenance/retention. Nếu yêu cầu là đồng bộ cache, inotify phù hợp như tín hiệu “có thể đã đổi”, còn scan/stat/checksum quyết định trạng thái thực.
10. Theo dõi và sizing ba giới hạn kernel
cat /proc/sys/fs/inotify/max_user_instances
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_queued_events
max_user_instances: số inotify instance theo real UID.max_user_watches: tổng watch theo real UID.max_queued_events: queue tối đa của một instance khi khởi tạo.
ENOSPC từ inotify_add_watch() thường nghĩa đạt watch limit hoặc thiếu resource, không nhất thiết disk hết chỗ. Trong container, nhiều workload có thể chia real UID/host kernel limit. Trước khi tăng sysctl, đếm directory thật, ước tính kernel memory, đặt giới hạn workload và xử lý leak watch.
11. Watch descriptor cần lifecycle và generation rõ ràng
Khi watch bị loại, IN_IGNORED có thể vẫn nằm trong queue. Không tái sử dụng mapping một cách mù quáng. Lưu wd cùng generation hoặc object riêng, xử lý remove idempotent và xóa pending rename liên quan khi subtree biến mất.
inotify_add_watch() trên object đã được watch có thể cập nhật/thay mask. Dùng IN_MASK_ADD nếu muốn hợp nhất hoặc IN_MASK_CREATE khi muốn phát hiện watch đã tồn tại theo support của kernel. Centralize thao tác add/remove để nhiều module không vô tình ghi đè mask của nhau.
12. Symlink, hard link, mount và network filesystem
- Quyết định có follow symlink hay dùng
IN_DONT_FOLLOW; chặn traversal thoát root. - Hard link là nhiều path tới cùng inode; event và path cache cần semantics rõ.
IN_UNMOUNTvàIN_IGNOREDyêu cầu đưa scope về trạng thái unhealthy.- Bind mount, overlay filesystem và container namespace có thể thay đổi góc nhìn path.
- Network filesystem có semantics/cache khác; thay đổi từ máy khác có thể không tạo event như mong đợi.
Test trên đúng filesystem/mount topology production. Không suy từ ext4 local sang NFS, overlayfs hoặc volume container.
13. Kiến trúc watcher có thể tự phục hồi
filesystem
-> inotify reader (drain nhanh)
-> bounded normalizer queue
-> state reducer theo generation
-> durable checkpoint / downstream output
periodic reconciler
-> scan + stat/hash
-> compare desired vs cached state
-> repair watch map and emit corrections
Reducer nên idempotent: tạo lại entry đã tồn tại hoặc xóa entry đã mất không làm hỏng state. Lưu generation của snapshot; event nhận trong lúc rescan được áp lên generation mới hoặc kích hoạt pass kế tiếp. Một reconcile định kỳ với jitter còn giúp bắt các lỗi không tạo overflow rõ ràng.
14. Kiểm thử các race thay vì chỉ thử touch file
- Tạo/xóa hàng nghìn file trong burst để ép queue pressure.
- Rename qua nhiều directory, ra/vào root và đổi tên cả subtree.
- Atomic replace file cấu hình khi process đang đọc.
- Tạo directory rồi lập tức tạo cây con trước khi watcher thêm watch.
- Xóa và tạo lại root, unmount/remount filesystem.
- Tạm dừng consumer, gây overflow, rồi kiểm tra rescan phục hồi đúng.
- Restart process và so sánh snapshot trước/sau.
- Chạy trên filesystem/container topology giống production.
Oracle của test phải là trạng thái filesystem sau cùng, không phải chuỗi event “đẹp”. Event có thể khác mà state cuối vẫn đúng; ngược lại, xử lý hết event chưa chứng minh cache đúng sau overflow.
Checklist production
- Dùng
IN_NONBLOCK|IN_CLOEXECvà drain tớiEAGAIN. - Map
wdsang directory/generation, không lưu pointer tạm. - Recursive watch bao phủ directory mới và có bootstrap reconciliation.
- Rename được ghép theo cookie qua nhiều batch và có timeout/fallback.
- Atomic replace được xử lý bằng watch directory cha.
IN_Q_OVERFLOWkích hoạt full rescan, không chỉ ghi log.- Queue nội bộ có giới hạn và backpressure rõ ràng.
- Sysctl, watch count, overflow, rescan duration và event lag được giám sát.
- Symlink/mount/network filesystem có policy và test riêng.
- Reconcile định kỳ và fault test chứng minh state cuối chính xác.
Kết luận
inotify là cơ chế thông báo hiệu quả, không phải nguồn sự thật tuyệt đối. File watcher đáng tin cậy coi event là tín hiệu tăng tốc cho một state machine có thể đối soát: watch từng directory, giữ mapping có lifecycle, ghép rename cẩn thận, xử lý atomic replace và phục hồi bằng rescan khi queue overflow. Khi trạng thái cuối được kiểm chứng độc lập với chuỗi event, watcher mới chịu được burst, race và restart của môi trường production.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.