본문 바로가기

코딩(Coding)

Ruby Tip: 인터럽트 후 종료하기 전에 메소드를 종료하는 방법

반응형

Ruby Tip: 인터럽트 후 종료하기 전에 메소드를 종료하는 방법

 

Unsplash의 Thomas Bormans 사진

축하합니다! 당신은 떠오르는 스타트업에 채용되었습니다. 핫테이크용 핫케이크. 귀하의 비즈니스는 최고의 새 트윗을 찾고 작성자에게 팬케이크를 보내는 것입니다. 수익은 걱정하지 마세요. 나중에 알아보겠습니다.

당신의 스타트업의 방탄 비즈니스 모델. Pexels의 Ash님이 제공한 팬케이크 사진

귀하의 비즈니스는 케이크가 갓 만든 상태로 도착한다는 사실에 달려 있습니다. 아무도 차가운 팬케이크를 좋아하지 않습니다. 차가운 팬케이크는 당신이 해결하기 위해 고용된 핵심 문제입니다. 설명을 드리겠습니다.

핫케이크 웹 앱에 트윗을 제출하는 트윗 찾기 영어 전공자를 유리하게 고용합니다. 그러나 열심히 일하는 팬케이크 요리사 네트워크는 포의 팬케이크 파티 (PPP). Po의 소프트웨어는 로컬 네트워크 API를 통해 새로운 주문을 수락한 다음 텔넷과 캐리어 비둘기를 혼합하여 귀하의 요리사에게 주문을 배포합니다.

어떻게든 인터넷에서 PPP로 주문을 받아야 하지만 방화벽을 통해 공개 인터넷 트래픽을 15년 된 독점 소프트웨어로 전달하면 좋은 결과가 없을 것이라고 확신합니다. 그래서 당신은 다른 접근 방식을 생각해냅니다.

PPP를 인터넷에 노출하는 것을 원하지 않기 때문에 새로운 주문에 대해 온라인에서 무언가를 쿼리한 다음 PPP에 삽입할 중개자가 필요합니다. 웹 앱을 쿼리할 수 있지만 웹 앱이 PPP에 입력된 주문을 추적하는 방법을 파악해야 합니다.

대신 Amazon SQS와 같은 온라인 대기열 서비스를 사용하도록 웹 앱을 설정합니다. PPP의 워크스테이션에서 실행되는 지속적으로 반복되는 Ruby 스크립트를 통해 대기열을 쿼리합니다. 스크립트는 다음을 수행합니다.

  1. 요청 받기: 온라인 대기열에서 새 주문 요청을 쿼리합니다. 대기열 서비스가 응답하면 1분 동안 다른 쿼리(예: 스크립트의 다른 복사본)에서 요청을 숨깁니다. SQS는 이것을 가시성 시간 초과.
  2. 주문 생성: 메시지 수신 시 로컬 네트워크 API를 통해 PPP에서 주문 생성
  3. 요청 확인: 표시 시간 제한이 만료될 때 다시 처리되지 않도록 대기열의 요청을 처리된 것으로 표시합니다.

전반적으로 이 구현은 다음과 같은 것을 에뮬레이트할 수 있습니다.

❯ ruby naive.rb
[INFO 2022-04-04 20:53:02 -0400] #--- Starting Worker Program ---#
[INFO 2022-04-04 20:53:02 -0400] Start operation, iteration 0. Part one: Eg. we might long poll for messages here
[INFO 2022-04-04 20:53:07 -0400] Operation part two. Eg. we might process messages here.
[INFO 2022-04-04 20:53:08 -0400] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-04-04 20:53:09 -0400] Operation, iteration 0, complete.
[INFO 2022-04-04 20:53:09 -0400] Start operation, iteration 1. Part one: Eg. we might long poll for messages here
...

쉬운.

기다리다. 2단계 '주문 생성'은 성공했지만 3단계 '승인' 전에 스크립트가 충돌하거나 종료되면 어떻게 됩니까? 우리는 주문을 생성했지만 요청 대기열에 요청을 남겼습니다.

스크립트 백업을 시작할 때 메시지를 다시 처리합니다. 우리는 적어도 두 개의 주문을 생성할 것입니다.

좋아, 우리가 작전의 순서를 바꾸면 어떻게 될까? 메시지를 받는 즉시 처리된 것으로 표시할 수 있으며 그 다음에 PPP에서 주문을 생성합니다.

그러나 요청을 확인한 후 충돌이 발생하지만 주문을 생성하기 전에 주문이 생성되지 않습니다. 누군가는 팬케이크를 얻지 못할 것입니다!

두 주문의 팬케이크를 배달하는 것이 전혀 배달하지 않는 것보다 덜 나쁘다고 결정하고 원래의 작업 순서를 고수합니다. 그러나 트윗당 여러 팬케이크 주문은 이상적이지 않습니다. 이 중복 팬케이크 문제는 스크립트 a) 충돌 또는 b) 코드의 이 '중요 섹션' 동안 종료되는 경우 발생합니다.

좋은 소식이 있습니다. 이 문제에 관련된 모든 행위자(Hot Cakes 웹 앱, SQS 및 Po's Pancake Party)는 스크립트에 영향을 미치는 주요 변경 사항이나 버그가 없을 것 같은 상당히 성숙한 프로그램입니다. 처음부터 스크립트가 작동하도록 할 수 있는 한 계속 작동할 것입니다. 스크립트 충돌 가능성이 낮습니다.

스크립트 종료 가능성이 매우 높습니다. 워크스테이션이 다시 시작되거나 누군가가 루비 스크립트를 종료하려고 할 때마다 발생합니다. 그러나 스크립트 종료도 제어할 수 있습니다! 종료하기 전에 제대로 작동하는 운영 체제가 메시지를 보내고 정리할 기회를 제공합니다. 따라서 최소한 일반 출구의 경우 ~해야한다 프로그램을 종료하기 전에 크리티컬 섹션 처리를 완료할 수 있습니다.

여기서 '정상' 종료는 운영 체제가 프로그램에 정상적인 종료 기회를 제공하는 종료를 의미합니다. UNIX bash에서 이러한 상황은 프로그램이 SIGINT, SIGTERM 또는 SIGQUIT로 전송되지만 SIGKILL 또는 SIGSTOP이 전송되지 않는 상황입니다. Windows 사용자인 경우 프로그램을 닫을 때(정상 종료)와 프로그램 응답 없음 대화 상자가 표시되고 "지금 끝내기"를 클릭할 때의 차이입니다.

프로그램을 중간에 종료하면 다음과 같습니다.

❯ ruby naive.rb
[INFO 2022-04-04 20:53:02 -0400] #--- Starting Worker Program ---#
[INFO 2022-04-04 20:53:02 -0400] Start operation, iteration 0. Part one: Eg. we might long poll for messages here
[INFO 2022-04-04 20:53:07 -0400] Operation part two. Eg. we might process messages here.
[INFO 2022-04-04 20:53:08 -0400] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-04-04 20:53:09 -0400] Operation, iteration 0, complete.
[INFO 2022-04-04 20:53:09 -0400] Start operation, iteration 1. Part one: Eg. we might long poll for messages here
^Cnaive.rb:5:in `sleep': Interrupt
from naive.rb:5:in `operation'
from naive.rb:29:in `
'

Ruby가 운영 체제에서 종료 시스템을 수신하면 SignalException. 주 스레드는 해당 예외를 처리하기 위해 점프합니다. 당신은 할 수 있습니다 rescue 인터럽트가 발생했을 때 실행 중인 블록을 래핑하여 처리합니다.

그럼에도 불구하고 메소드 실행을 중단하고 실행이 구조 블록으로 이동합니다. 이것은 우리의 문제를 해결하지 못합니다!

❯ ruby signal_trap.rb
[INFO 2022-04-04 20:49:22 -0400] #--- Starting Worker Program ---#
[INFO 2022-04-04 20:49:22 -0400] Start operation, iteration 0. Part one: Eg. we might long poll for messages here
[INFO 2022-04-04 20:49:27 -0400] Operation part two. Eg. we might process messages here.
[INFO 2022-04-04 20:49:28 -0400] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-04-04 20:49:29 -0400] Operation, iteration 0, complete.
[INFO 2022-04-04 20:49:29 -0400] Start operation, iteration 1. Part one: Eg. we might long poll for messages here
^C[WARN 2022-04-04 20:49:33 -0400] #--- Received interrupt ---#❯

또 다른 옵션은 at_exit 차단하다. 이 블록의 코드는 인터럽트에 대한 응답으로 프로그램이 종료될 때 실행됩니다.

이 접근 방식은 좀 더 간결하지만 신호 트랩과 같은 문제가 있습니다. 프로그램을 중단하면 여전히 코드 실행이 중단되고 at_exit 차단하다. 이것은 여전히 우리의 문제를 해결하지 못합니다!

❯ ruby at_exit.rb
[INFO 2022-04-04 21:00:40 -0400] #--- Starting Worker Program ---#
[INFO 2022-04-04 21:00:40 -0400] Start operation, iteration 0. Part one: Eg. we might long poll for messages here
[INFO 2022-04-04 21:00:45 -0400] Operation part two. Eg. we might process messages here.
[INFO 2022-04-04 21:00:46 -0400] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-04-04 21:00:47 -0400] Operation, iteration 0, complete.
[INFO 2022-04-04 21:00:47 -0400] Start operation, iteration 1. Part one: Eg. we might long poll for messages here
^C[WARN 2022-04-04 21:00:48 -0400] #--- Received interrupt ---#
at_exit.rb:5:in `sleep': Interrupt
from at_exit.rb:5:in `operation'
from at_exit.rb:35:in `
'

이러한 접근 방식 중 어느 것도 작동하지 않는 이유는 메인 스레드가 인터럽트를 처리하기 위해 실행을 벗어나는 것을 막을 수 없기 때문입니다. 대신 메서드 실행을 메인 스레드에서 분리하여 인터럽트에 의해 일시 중지되도록 해야 합니다.

이렇게 하기 위해 두 번째 스레드를 사용하여 인터럽트 처리로부터 안전한 'uninterruptable' 메서드를 실행할 수 있습니다. 그런 다음 프로그램을 '정리'할 수 있습니다. at_exit 중단할 수 없는 메서드가 완료될 때까지 기다리면서 차단합니다.

❯ ruby at_exit_uninterruptable.rb
[INFO 2022-03-12 12:52:21 -0500] #--- Starting Worker Program ---#
[INFO 2022-03-12 12:52:21 -0500] Start operation, iteration 0. Part one: Eg. we might long poll for messages here
[INFO 2022-03-12 12:52:26 -0500] Operation part two. Eg. we might process messages here.
[INFO 2022-03-12 12:52:27 -0500] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-03-12 12:52:28 -0500] Operation, iteration 0, complete.
[INFO 2022-03-12 12:52:28 -0500] Start operation, iteration 1. Part one: Eg. we might long poll for messages here
^C[WARN 2022-03-12 12:52:30 -0500] #--- INTERRUPT RECEIVED ---#
[WARN 2022-03-12 12:52:30 -0500] Waiting for current iteration to complete. Interrupt again for immediate (unsafe) termination.
[INFO 2022-03-12 12:52:33 -0500] Operation part two. Eg. we might process messages here.
[INFO 2022-03-12 12:52:34 -0500] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-03-12 12:52:35 -0500] Operation, iteration 1, complete.
[INFO 2022-03-12 12:52:35 -0500] #--- ITERATION COMPLETED. EXITING SAFELY ---#
delay_script_exit.rb:51:in `join': Interrupt
from delay_script_exit.rb:51:in `
'

효과가있다!

그리고 스택 추적보다 종료 시 더 예쁜 출력을 원하면 다음을 통해 스레드를 중지할 수도 있습니다. SignalException 덫.

❯ ruby signal_trap_uninterruptable.rb
[INFO 2022-04-04 21:12:23 -0400] #--- Starting Worker Program ---#
[INFO 2022-04-04 21:12:23 -0400] Start operation, iteration 0. Part one: Eg. we might long poll for messages here
[INFO 2022-04-04 21:12:28 -0400] Operation part two. Eg. we might process messages here.
[INFO 2022-04-04 21:12:29 -0400] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-04-04 21:12:30 -0400] Operation, iteration 0, complete.
[INFO 2022-04-04 21:12:30 -0400] Start operation, iteration 1. Part one: Eg. we might long poll for messages here
^C[WARN 2022-04-04 21:12:33 -0400] #--- INTERRUPT RECEIVED ---#
[WARN 2022-04-04 21:12:33 -0400] Waiting for current iteration to complete. Interrupt again for immediate (unsafe) termination.
[INFO 2022-04-04 21:12:35 -0400] Operation part two. Eg. we might process messages here.
[INFO 2022-04-04 21:12:36 -0400] Operation part three. Eg. we might acknowledge the message as processed here.
[INFO 2022-04-04 21:12:37 -0400] Operation, iteration 1, complete.
[INFO 2022-04-04 21:12:37 -0400] #--- ITERATION COMPLETED. EXITING SAFELY ---#❯

그리고 그게 다야! 신호 인터럽트를 캡처하고 종료하기 전에 중단할 수 없는 작업이 완료될 때까지 기다렸습니다. 즐거운 코딩!

반응형