CRuby의 스레드 스케줄러는 효율성과 공정성을 확보하기 위해 스레드 실행 시간 공유, 블로킹 작업 처리, 우선순위 조정, 제어 전달, 락킹 관리 등 다양한 역할을 수행합니다. 이러한 스레드 관리는 VM 체크와 함수를 통해 구현되며, ‘인터럽트’라는 핵심 개념을 중심으로 이루어집니다. CRuby는 타이머, 트랩, 종료, VM 배리어, 연기된 작업, 보류 중인 작업 등 여러 유형의 인터럽트 이벤트를 정의하고 처리합니다.
인터럽트 마스크의 작동 원리
CRuby는 TIMER_INTERRUPT_MASK와 같은 정수형 비트마스크를 사용하여 여러 프로그램 상태를 하나의 숫자로 효율적으로 표현합니다. 각 상태는 2의 거듭제곱으로 표현되는 단일 비트에 저장되며, 이를 통해 최대 64개의 독립적인 on/off 상태를 나타낼 수 있습니다. 비트와이즈 연산(AND, OR, XOR, NOT)을 통해 이러한 상태를 빠르고 효율적으로 확인하고 설정할 수 있습니다.
interrupt_flag는 실행 대기 중인 인터럽트 작업을, interrupt_mask는 현재 실행이 차단된 인터럽트 작업을 나타냅니다. RUBY_VM_INTERRUPTED_ANY 매크로는 (@interrupt_flag & ~@interrupt_mask) != 0 연산을 통해 인터럽트 발생 여부를 판단합니다.
인터럽트 검사 및 처리 메커니즘
Ruby VM은 branchif와 같은 조건문, 메서드 호출(opt_send_without_block), IO 작업 등 다양한 내부 체크포인트에서 RUBY_VM_CHECK_INTS를 통해 인터럽트 큐를 빈번하게 확인합니다. 인터럽트가 감지되면 rb_threadptr_execute_interrupts 함수가 호출되어 실제 인터럽트 처리 로직을 수행합니다. 이 함수는 while 루프를 사용하여 여러 인터럽트가 동시에 발생하거나 처리 중에 새로운 인터럽트가 설정되어도 모두 처리될 때까지 반복합니다.
주요 인터럽트 유형 및 기능
-
TIMER_INTERRUPT_MASK: 스레드 시간 할당량(기본 100ms)을 관리하고, 스레드 우선순위에 따라 실행 시간을 조정합니다.rb_thread_schedule_limits를 통해 스레드의 실행 시간이 할당량을 초과하면 스케줄링을 재조정하여 제어권을 다른 스레드에 넘깁니다. Ruby 3.4부터는RUBY_THREAD_TIMESLICE환경 변수를 통해 이 기본 할당량을 조정할 수 있습니다. -
TRAP_INTERRUPT_MASK: POSIX 시그널 처리(`Signal
trap)를 담당하며, Ruby 3.4+부터는 Ractor의 require 및 autoload와 같은 작업을 메인 Ractor 스레드에서 비동기적으로 실행하는 interrupt_exec_tasks` 메커니즘에도 사용됩니다.
PENDING_INTERRUPT_MASK: `Thread
raise 및 Thread
kill과 같은 호출로 인해 스레드의 pending_interrupt_queue에 대기 중인 예외나 종료 요청을 처리합니다. 예외는 rb_exc_raise를 통해 발생시키고, 종료 요청은 rb_threadptr_to_kill`을 통해 스레드를 즉시 종료합니다.
TERMINATE_INTERRUPT_MASK: 주로 `Thread
kill` 호출 또는 Ruby 프로세스 종료 시 모든 Ractor 스레드를 종료하는 데 사용됩니다.
-
POSTPONED_JOB_INTERRUPT_MASK: 현재 컨텍스트에서 안전하게 실행할 수 없는 작업을postponed_job_queue에 넣어 안전한 시점에 실행합니다. 주로 Tracepoint 내부에서 활용됩니다. -
VM_BARRIER_INTERRUPT_MASK: YJIT 컴파일과 같이 VM 전체에 대한 독점적인 접근이 필요한 작업 시, 모든 Ractor 스레드를 safepoint에 도달시켜 대기시키는 데 사용됩니다. 이는 Ruby 3에서 Ractor가 도입되면서 VM 전체의 일관성을 유지하기 위해 중요한 역할을 합니다.