Nếu bạn từng xem xét hệ thống phân cấp ngoại lệ của Ruby, có thể đã nhận ra điều gì đó kỳ lạ. Ngoài các ngoại lệ thông thường như RuntimeError và NoMethodError, còn có một tham chiếu lạ đến Errno::*.
Exception StandardError ... SystemCallError Errno::* ...
Nếu bạn từng bị lỗi khi ghi đĩa khi ổ cứng đầy, hoặc cố gọi API qua mạng không ổn định, bạn chắc chắn đã gặp loại lỗi này. Bạn có thể kích hoạt nó ngay bằng cách cố mở file không tồn tại.
irb> File.open("badfilename.txt") Errno::ENOENT: No such file or directory @ rb_sysopen - badfilename.txt from (irb):9:in `initialize' from (irb):9:in `open' from (irb):9 from /Users/snhorne/.rbenv/versions/2.1.0/bin/irb:11:in `<main>'
Nhưng ngoại lệ Errno chính xác là gì? Và tại sao chúng được xử lý khác với các loại ngoại lệ khác?
Thích nghi Ruby với hệ điều hành
Các ngoại lệ Errno về cơ bản là một bộ chuyển đổi (adapter). Chúng kết nối lỗi của hệ điều hành với hệ thống ngoại lệ của Ruby. Hệ điều hành xử lý lỗi theo cách khác so với Ruby, nên bạn bắt buộc phải có một bộ chuyển đổi.
Trong Ruby, lỗi thường được báo cáo dưới dạng ngoại lệ. Nhưng lỗi hệ điều hành thường chỉ là các số nguyên. Vì vậy Ruby định nghĩa một class ngoại lệ cho từng lỗi OS có thể xảy ra. Sau đó đưa tất cả các ngoại lệ này vào một module gọi là Errno.
Chúng ta có thể dùng IRB để xem tất cả ngoại lệ trong module này. Và thật nhiều!
irb> Errno.constants => [:NOERROR, :EPERM, :ENOENT, :ESRCH, :EINTR, :EIO, :ENXIO, :E2BIG, :ENOEXEC, :EBADF, :ECHILD, :EAGAIN, :ENOMEM, :EACCES, :EFAULT, :ENOTBLK, :EBUSY, :EEXIST, :EXDEV, :ENODEV, :ENOTDIR, :EISDIR, :EINVAL, :ENFILE, :EMFILE, :ENOTTY, :ETXTBSY, :EFBIG, :ENOSPC, :ESPIPE, :EROFS, :EMLINK, :EPIPE, :EDOM, :ERANGE, :EDEADLK, :ENAMETOOLONG, :ENOLCK, :ENOSYS, :ENOTEMPTY, :ELOOP, :EWOULDBLOCK, :ENOMSG, :EIDRM, :ECHRNG, :EL2NSYNC, :EL3HLT, :EL3RST, :ELNRNG, :EUNATCH, :ENOCSI, :EL2HLT, :EBADE, :EBADR, :EXFULL, :ENOANO, :EBADRQC, :EBADSLT, :EDEADLOCK, :EBFONT, :ENOSTR, :ENODATA, :ETIME, :ENOSR, :ENONET, :ENOPKG, :EREMOTE, :ENOLINK, :EADV, :ESRMNT, :ECOMM, :EPROTO, :EMULTIHOP, :EDOTDOT, :EBADMSG, :EOVERFLOW, :ENOTUNIQ, :EBADFD, :EREMCHG, :ELIBACC, :ELIBBAD, :ELIBSCN, :ELIBMAX, :ELIBEXEC, :EILSEQ, :ERESTART, :ESTRPIPE, :EUSERS, :ENOTSOCK, :EDESTADDRREQ, :EMSGSIZE, :EPROTOTYPE, :ENOPROTOOPT, :EPROTONOSUPPORT, :ESOCKTNOSUPPORT, :EOPNOTSUPP, :EPFNOSUPPORT, :EAFNOSUPPORT, :EADDRINUSE, :EADDRNOTAVAIL, :ENETDOWN, :ENETUNREACH, :ENETRESET, :ECONNABORTED, :ECONNRESET, :ENOBUFS, :EISCONN, :ENOTCONN, :ESHUTDOWN, :ETOOMANYREFS, :ETIMEDOUT, :ECONNREFUSED, :EHOSTDOWN, :EHOSTUNREACH, :EALREADY, :EINPROGRESS, :ESTALE, :EUCLEAN, :ENOTNAM, :ENAVAIL, :EISNAM, :EREMOTEIO, :EDQUOT, :ECANCELED, :EKEYEXPIRED, :EKEYREJECTED, :EKEYREVOKED, :EMEDIUMTYPE, :ENOKEY, :ENOMEDIUM, :ENOTRECOVERABLE, :EOWNERDEAD, :ERFKILL, :EAUTH, :EBADRPC, :EDOOFUS, :EFTYPE, :ENEEDAUTH, :ENOATTR, :ENOTSUP, :EPROCLIM, :EPROCUNAVAIL, :EPROGMISMATCH, :EPROGUNAVAIL, :ERPCMISMATCH, :EIPSEC]
Nhưng tại sao chúng được đặt tên bí ẩn như vậy? Tôi phải làm sao đoán được ENOENT có nghĩa là "File not found"?
...Thực ra có một câu trả lời rất đơn giản.
Sao chép trực tiếp từ libc
Người đầu tiên xây dựng module Errno chỉ sao chép tên lỗi trực tiếp từ libc. Vì vậy ENOENT, trong C, là tên của một macro chứa mã lỗi số nguyên mà OS trả về khi không tìm thấy file.
Vì vậy, để thực sự hiểu từng lỗi làm gì, thủ thuật là xem tài liệu thư viện C chuẩn. Bạn có thể tìm danh sách lớn ở đây. Tôi trích xuất một vài lỗi phổ biến hơn dưới đây:
| Mã lỗi | Ý nghĩa |
|---|---|
| EPERM | Operation not permitted; bạn không thể truy cập file trừ khi có quyền. |
| ENOENT | File or directory not found. |
| EIO | Input/output error; thường dùng cho lỗi đọc/ghi vật lý. |
| EBADF | Bad file descriptor. Bạn sẽ gặp lỗi này nếu cố ghi vào file chỉ mở để đọc. |
| ECHILD | Bạn cố thao tác tiến trình con, nhưng không có tiến trình con nào. |
| ENOMEM | Hết RAM, không thể cấp phát thêm bộ nhớ ảo. |
| EACCES | Permission denied; quyền file không cho phép thao tác. |
| ENOTBLK | Bạn cố mount file thường như thiết bị, ví dụ HDD. |
| EBUSY | Resource busy; tài nguyên hệ thống không thể chia sẻ đang được dùng. |
| EEXIST | File exists; file đã tồn tại được chỉ định ở ngữ cảnh chỉ hợp lý với file mới. |
| ENOTDIR | File không phải thư mục được chỉ định khi cần thư mục. |
| EISDIR | File là thư mục; không thể mở thư mục để ghi, hoặc tạo/xóa hard link tới nó. |
| EINVAL | Invalid argument. Dùng để chỉ ra các vấn đề truyền sai đối số cho hàm thư viện. |
| EMFILE | Quá nhiều file mở trong tiến trình hiện tại, không thể mở thêm. |
| EFBIG | File too big; kích thước file vượt quá giới hạn cho phép. |
| ENOSPC | No space left on device; ghi file thất bại vì ổ đĩa đầy. |
| ESPIPE | Invalid seek operation (như trên pipe). |
| EROFS | Cố sửa đổi thứ gì đó trên hệ thống file read-only. |
| EPIPE | Broken pipe; không có tiến trình nào đọc từ đầu kia của pipe. |
| ENOTSOCK | File không phải socket được chỉ định khi cần socket. |
| ENETUNREACH | Thao tác socket thất bại vì subnet chứa host từ xa không thể truy cập. |
| ENETRESET | Kết nối mạng bị reset vì host từ xa crash. |
| ECONNABORTED | Kết nối mạng bị hủy cục bộ. |
| ECONNRESET | Kết nối mạng bị đóng vì lý do ngoài tầm kiểm soát (remote reboot, vi phạm giao thức). |
| ENOBUFS | Buffer kernel cho I/O đang đầy. Trong GNU, luôn đồng nghĩa với ENOMEM. |
| EISCONN | Cố kết nối socket đã kết nối. |
| ENOTCONN | Socket chưa kết nối. Xảy ra khi truyền dữ liệu mà chưa chỉ định đích. |
| EDESTADDRREQ | Không có địa chỉ đích mặc định cho socket. |
| ESHUTDOWN | Socket đã bị shutdown. |
| ETIMEDOUT | Thao tác socket với timeout không nhận phản hồi trong thời gian chờ. |
| ECONNREFUSED | Host từ xa từ chối kết nối (thường do không chạy dịch vụ yêu cầu). |
| EHOSTDOWN | Host từ xa cho kết nối mạng đang down. |
| EHOSTUNREACH | Host từ xa không thể truy cập. |
| ENOTEMPTY | Directory not empty, nơi mong đợi thư mục rỗng. Thường khi xóa thư mục. |
| EPROCLIM | Giới hạn tiến trình mới per-user sẽ bị vượt qua bởi fork. |