Ruby의 IO 메서드 재해석
Ruby의 IO 클래스는 C 언어의 STDIO 함수들을 객체 지향적인 메서드로 변환합니다. 예를 들어, fprintf는 `IO
printf로, fread는 IO
read로 매핑됩니다. 이 과정에서 file * 인수를 받는 함수들은 해당 IO 객체의 메서드로 전환되며, 함수 이름의 F 접두사는 제거됩니다. 특히, feof는 Ruby에서 eof?와 같이 술어 메서드로 변환되고, fsetpos는 pos=와 같은 속성 설정 메서드로 제공되어 Ruby의 관습에 맞춥니다. 이는 Ruby IO` 기능의 절반 이상이 유닉스 및 그 주변 시스템에서 유래했음을 보여주며, 유닉스 사용자에게 학습 부담을 줄이는 디자인입니다.
FEOF의 문제점과 Ruby의 개선
C 언어의 FEOF는 이미 EOF를 만났는지 여부를 확인하는 반면, 사용자들은 종종 ‘다음에 읽을 때 EOF가 될까요?’라는 파스칼 방식의 동작을 기대합니다. 이 오해는 ‘마지막 줄을 두 번 복사하는’ 코드와 같은 흔한 버그로 이어집니다. Ruby의 eof?는 사용자의 기대에 맞춰 내부적으로 버퍼링을 활용하여 실제 읽기를 시도함으로써 EOF 여부를 판단합니다. Ruby 1.9에서 STDIO를 버리면서 EOF 플래그를 구현하지 않기로 결정한 것은 tail -f와 같은 시나리오에서 파일이 확장될 때 EOF 이후에도 읽을 수 있도록 하기 위함이었습니다. 파이썬이 FEOF와 유사한 메서드를 제공하지 않는 것은 이러한 문제점을 인식한 훌륭한 판단으로 평가됩니다.
Select 시스템 호출과 Ruby의 IO.select
유닉스 select 시스템 호출은 커널 버퍼만 확인하며 프로세스 내 STDIO 버퍼를 고려하지 않아, STDIO와 함께 사용 시 데이터가 STDIO 버퍼에 있어도 select는 읽기 불가능으로 판단하는 문제가 발생합니다. Ruby IO.select는 이러한 문제를 해결하기 위해 프로세스 내 IO 버퍼를 고려하여, 버퍼에 데이터가 있으면 즉시 읽기 가능으로 판단하고, 버퍼가 비어있을 때만 select 시스템 호출을 사용하여 커널 버퍼를 확인합니다. 이는 Ruby 1.9에서 자체 버퍼링 코드를 구현함으로써 포터블하게 해결되었습니다.
Sysread 및 Read_partial, Read_nonblock
read 시스템 호출은 커널에서 직접 데이터를 읽어오므로, getc와 같은 STDIO 버퍼를 사용하는 메서드와 혼용 시 문제가 발생할 수 있습니다. Ruby는 sysread와 STDIO 기반 메서드의 혼용을 금지하여 예외를 발생시킴으로써 혼란을 방지합니다. 그러나 Perl은 이를 허용하여 예측 불가능한 동작을 초래합니다. 중계 애플리케이션(예: HTTP CONNECT 프록시)과 같이 getc로 헤더를 읽고 sysread로 본문을 전송하는 시나리오에서는 sysread가 예외를 발생시킬 수 있습니다. 이를 해결하기 위해 read_partial과 같은 메서드가 제안되었으며, 이는 프로세스 내 버퍼를 먼저 확인하고, 논블로킹 예외 발생 시 select로 대기 후 재시도하는 로직을 내부적으로 처리하여 사용자 코드의 복잡성을 줄입니다. read_nonblock 및 write_nonblock은 명시적으로 블로킹하지 않는 동작을 보장하여 특정 시나리오에서의 제어력을 높입니다.
기타 유닉스 수정주의 사례
-
시간 함수: Ruby는 POSIX에 없는
Time.gm과 같은 함수를 제공하고,struct tm의 월(month) 인덱스를 0-11이 아닌 1-12로 변경하여 사용자 친화성을 높입니다. -
감마 함수: 유닉스/BSD의
gamma함수 이름 오류(log_gamma여야 함)를 Ruby는Math.lgamma와Math.gamma로 올바르게 구분하여 제공합니다. -
find -depth:find명령의-depth옵션이 ‘깊이 우선’이 아닌 ‘귀환 순서’를 의미하는 이름 오류를 지적하며, Ruby의Find라이브러리에서 이 오류를 반복하지 말 것을 주장합니다. -
FD_CLOEXEC기본값:fork및exec시 파일 디스크립터(FD)가 자식 프로세스에 상속되는 유닉스의 기본 동작이 의도치 않은 문제를 야기할 수 있음을 지적하며, Ruby에서FD_CLOEXEC를 기본값으로 설정할 것을 제안합니다. -
openat등 새로운 시스템 호출:openat,unlinkat등 상대 경로의 기준점을 파일 디스크립터로 지정하는 새로운 유닉스 시스템 호출을 Ruby에 적용하는 다양한 방안을 논의합니다. -
프로세스 ID 객체화:
Process와Thread의 유사성에도 불구하고waitpid,join등 API가 다른 점을 지적하며,PID를 객체화하여Thread와 유사하게 Duck Typing으로 처리할 것을 제안합니다. -
system호출의Ctrl+C동작:system호출로 실행된 자식 프로세스에Ctrl+C가 전달될 때 Ruby 프로세스가 죽지 않는 유닉스의 전통적 동작이 Ruby의 일반적인 사용 사례에는 부적합할 수 있음을 지적하며, 기본 동작 변경 또는 옵션 추가를 제안합니다. -
비동기 시그널: 유닉스의 비동기 시그널 처리의 어려움을 언급하며,
pthread_sigmask나 Java처럼 더 나은 시그널 처리 방안을 Ruby에 도입할 필요성을 제기합니다.