หากคุณเคยดู hierarchy ของ Ruby exceptions คุณอาจสังเกตเห็นสิ่งแปลกๆ นอกจาก exceptions ธรรมดาเช่น RuntimeError และ NoMethodError แล้ว ยังมีการอ้างอิงถึง Errno::* อีกด้วย
Exception
StandardError
...
SystemCallError
Errno::*
...
หากคุณเคยเขียนข้อมูลลงดิสก์ที่เต็ม หรือพยายามเรียก API ผ่านเครือข่ายที่ล้มเหลว คุณน่าจะเคยเจอ error ประเภทนี้ คุณสามารถทดสอบได้ทันทีโดยพยายามเปิดไฟล์ที่ไม่มีอยู่จริง:
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 `'
แต่ Errno exceptions คืออะไรจริงๆ? และทำไมจึงถูกจัดการแตกต่างจาก exceptions อื่นๆ?
การปรับ Ruby ให้เข้ากับระบบปฏิบัติการ
Errno exceptions เป็นตัวแปลง (adapter) ที่เชื่อมต่อข้อผิดพลาดของระบบปฏิบัติการ (OS) เข้ากับระบบ exception ของ Ruby OS จัดการข้อผิดพลาดแตกต่างจาก Ruby จึงจำเป็นต้องมีตัวแปลง
ใน Ruby ข้อผิดพลาดมักรายงานเป็น exceptions แต่ข้อผิดพลาดของ OS มักเป็นเพียงตัวเลขจำนวนเต็ม (integers) ดังนั้น Ruby จึงกำหนดคลาส exception หนึ่งสำหรับแต่ละรหัสข้อผิดพลาดของ OS แล้วนำ exceptions ทั้งหมดใส่ไว้ใน module ชื่อ Errno
เราสามารถใช้ IRB ดู exceptions ทั้งหมดใน module นี้ได้ และมีเยอะมาก!
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]
แต่ทำไมชื่อถึงลึกลับมาก? ฉันจะเดาได้อย่างไรว่า ENOENT หมายถึง "File not found"?
...คำตอบง่ายมาก
คัดลอกมาจาก libc โดยตรง
ผู้สร้าง module Errno คัดลอกชื่อ error มาจาก libc โดยตรง ดังนั้น ENOENT ใน C คือชื่อของ macro ที่เก็บรหัสข้อผิดพลาดจำนวนเต็มที่ OS คืนค่าเมื่อไม่พบไฟล์
เพื่อรู้ว่าแต่ละ error ทำอะไร วิธีคือดู documentation ของ C standard library นี่คือบางส่วนที่เกี่ยวข้องมากกว่า:
| Error Code | ความหมาย |
|---|---|
| EPERM | Operation not permitted; ไม่สามารถเข้าถึงไฟล์ได้หากไม่มีสิทธิ์ |
| ENOENT | File or directory not found |
| EIO | Input/output error; ใช้สำหรับข้อผิดพลาดการอ่าน/เขียนทางกายภาพ |
| EBADF | Bad file descriptor; เช่นพยายามเขียนไฟล์ที่เปิดเฉพาะอ่าน |
| ECHILD | พยายามจัดการ child process แต่ไม่มี child process ใดๆ |
| ENOMEM | หน่วยความจำ RAM หมด ไม่สามารถจัดสรร virtual memory เพิ่มได้ |
| EACCES | Permission denied; สิทธิ์ไฟล์ไม่อนุญาตให้ทำ operation นั้น |
| ENOTBLK | พยายาม mount ไฟล์ทั่วไปเป็น device เช่น HDD |
| EBUSY | Resource busy; system resource ที่แชร์ไม่ได้กำลังถูกใช้งาน |
| EEXIST | File exists; ระบุไฟล์ที่มีอยู่แล้วในบริบทที่ควรระบุไฟล์ใหม่ |
| ENOTDIR | ระบุไฟล์ที่ไม่ใช่ directory เมื่อต้องการ directory |
| EISDIR | File is a directory; ไม่สามารถเปิด directory เพื่อเขียน หรือสร้าง/ลบ hard links ได้ |
| EINVAL | Invalid argument; ใช้บ่งชี้ปัญหาต่างๆ จากการส่ง argument ผิดไปยัง library function |
| EMFILE | Process ปัจจุบันเปิดไฟล์เยอะเกินไป ไม่สามารถเปิดเพิ่มได้ |
| EFBIG | File too big; ขนาดไฟล์เกินกว่าที่ระบบอนุญาต |
| ENOSPC | No space left on device; เขียนไฟล์ล้มเหลวเพราะดิสก์เต็ม |
| ESPIPE | Invalid seek operation (เช่นบน pipe) |
| EROFS | พยายามแก้ไขบน read-only file system |
| EPIPE | Broken pipe; ไม่มี process อ่านจากปลายอีกข้างของ pipe |
| ENOTSOCK | ระบุไฟล์ที่ไม่ใช่ socket เมื่อต้องการ socket |
| ENETUNREACH | Socket operation ล้มเหลวเพราะ subnet ของ remote host ไม่สามารถเข้าถึงได้ |
| ENETRESET | Network connection ถูก reset เพราะ remote host crashed |
| ECONNABORTED | Network connection ถูก abort โดย local |
| ECONNRESET | Network connection ถูกปิดเพราะเหตุผลนอกเหนือจากการควบคุมของ local host |
| ENOBUFS | Kernel buffers สำหรับ I/O operations เต็มหมด |
| EISCONN | พยายาม connect socket ที่เชื่อมต่อแล้ว |
| ENOTCONN | Socket ไม่ได้เชื่อมต่อกับอะไรเลย |
| EDESTADDRREQ | ไม่มี default destination address สำหรับ socket |
| ESHUTDOWN | Socket ถูก shut down แล้ว |
| ETIMEDOUT | Socket operation ที่มี timeout ไม่ได้รับ response ภายในเวลาที่กำหนด |
| ECONNREFUSED | Remote host ปฏิเสธการเชื่อมต่อ (มักเพราะไม่ได้รัน service ที่ร้องขอ) |
| EHOSTDOWN | Remote host สำหรับ network connection ล้ม |
| EHOSTUNREACH | Remote host ไม่สามารถเข้าถึงได้ |
| ENOTEMPTY | Directory not empty เมื่อคาดหวังว่าเป็น directory ว่าง |
| EPROCLIM | Per-user limit on new process จะถูกเกินโดย fork ที่พยายามทำ |