코드 리로딩 구현을 위해 몇 가지 핵심 젬(concurrent-ruby, listen, puma, rack, zeitwerk)이 필요하며, 이는 Gemfile에 명시되어야 합니다. 애플리케이션 구조는 config/environment.rb
에서 환경을 로드하고, lib/my_app.rb
에서 Rack 애플리케이션을 정의하며, config.ru
를 통해 애플리케이션을 실행하는 형태로 구성됩니다.
첫 번째 유틸리티 클래스는 Once
입니다. 이 클래스는 특정 코드 블록이 여러 스레드에서 호출되더라도 단 한 번만 실행되도록 보장하는 역할을 하며, 이는 서버 포크 이전에 리스너 스레드를 시작하지 않도록 하는 데 중요하게 사용됩니다.
핵심적인 구성 요소는 CodeLoader
클래스입니다. 이 클래스는 Zeitwerk 로더를 초기화하고, 지정된 경로의 코드를 로드합니다. 개발 환경에서는 코드 리로딩을 활성화하고, Listen
젬을 사용하여 파일 시스템 변경 사항을 감지합니다. 또한, Concurrent::ReadWriteLock
을 사용하여 코드 리로딩 과정의 스레드 안전성을 확보합니다. reload!
메서드는 파일 변경이 감지될 때 실제 코드 리로딩을 수행하며, 이 과정에서 쓰기 잠금을 획득하여 다른 요청이 방해받지 않도록 합니다. 프로덕션 환경에서는 eager_load
를 통해 코드를 즉시 로드하여 copy-on-write 성능 이점을 활용합니다.
CodeLoaderMiddleware
는 Rack 미들웨어로 작동하며, 각 요청이 들어올 때마다 CodeLoader
의 reload!
메서드를 호출하여 코드 변경 여부를 확인하고 필요시 리로드합니다. 이 미들웨어는 또한 로더의 읽기 잠금을 사용하여 코드 리로딩 중에는 요청 처리를 지연시켜 스레드 안전한 환경을 유지합니다.
마지막으로, lib/my_app.rb
에서 CodeLoaderMiddleware
를 사용하도록 설정하고, 특히 개발 환경에서 코드 리로딩이 활성화된 경우 run
메서드를 Proc으로 래핑하여 매 요청마다 최신 버전의 애플리케이션 코드를 참조하도록 합니다. 이는 이전에 로드된 코드의 오래된 참조(stale reference)로 인해 코드 리로딩이 제대로 작동하지 않는 문제를 해결합니다.