본문 바로가기
개발과 기술

[Python] Python 일정 시간 후 timeout 에러 내는 방법

by growd 2020. 4. 14.

서버 사이드에서 어떤 작업을 하고 클라이언트 사이드로 응답을 하는 로직을 만드는 중 이었습니다.

사용하고 있는 서버 인프라가 특정 시간만큼만 작동하고 그 이상으로 작동할 시 timeout error를 내었습니다.

하지만 서버에서 해야하는 작업은 어떤 입력을 받았냐에 따라 얼마나 작동할 지 알 수 없는 상황이었습니다.

내린 결론은 특정 시간이 지나면 하던 작업을 마치고 클라이언트 사이드로 응답을 보내는 것이었습니다.

하지만 Python프로그램이 돌아가는 도중에 시간을 재고있던 다른 녀석이 돌아가는 녀석을 멈춰야 했습니다.

 

이 상황을 해결할 수 있는 것이  signal  모듈 입니다.

 

 

 


 

 

예제를 봅시다.

import signal
import time
 
class TimeOutException(Exception):
    pass
 
def alarm_handler(signum, frame):
    print("Time is up!")
    raise TimeOutException()
 
def loop_for_n_seconds(n):
    for sec in range(n):
        print("{} second!".format(sec))
        time.sleep(1)
 
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(4)

try:
    loop_for_n_seconds(6)
except TimeOutException as e:
    print(e)

위의 코드를 간단히 설명하자면 우선, 6초동안 돌아가는 loop가 있습니다.

그리고 4초 동안 작업을 마치지 않으면 TimeOutException을 일으키는 signal.alarm이 있습니다.

loop이 try except문 안에서 돌고 있고 4초안에 마칠 수 없기 때문에 TimeOutException이 일어나게 됩니다.

그 에러를 except문이 잡게 되고 에러를 출력합니다.

 

 


 

 

잘 이해가 안되는 분들을 위해 하나씩 뜯어 봅시다.

import signal
import time

우선 우리가 필요한 signal 모듈과 time 모듈을 import하여 옵니다.

이 부분부터 이해가 되시지 않는다면 힘차게 화이팅을 외쳐봅시다.

 

 


 

 

class TimeOutException(Exception):
    pass

간단하게 TimeOutException을 정의해 봅시다.

Exception에러를 상속하기만 하면 됩니다.

 

 

 


 

 

 

def alarm_handler(signum, frame):
    print("Time is up!")
    raise TimeOutException()

signal.alarm이 발생하게 되면 할 일들을 alaram_handler 내부에 정의해 줍니다.

파라미터는 이 예제에서는 수정할 필요가 없습니다.

signal.alarm이 발생하면 "Time is up!"이 출력되고 위에서 정의한  TimeOutExceptionraise(발생) 시킵니다.

 

 

 


 

 

def loop_for_n_seconds(n):
    for sec in range(n):
        print("{} second!".format(sec))
        time.sleep(1)

n의 숫자를 받으면 그 수초만큼 프로세스를 점유하여 1초마다 내부에 정의된 로직을 실행하게 할 수 있습니다.

물론 내부로직이 시간이 많이 걸린다면 이 예제에서 필요한 "1초마다"를 보장 할 수 없습니다.

이 예제에서는 단순히 몇초가 지났는지만 출력하기 때문에 약 1초마다 실행되는 것을 보장 할 수 있습니다.

time.sleep(1)이 부분이 1초동안 프로세스를 sleep시킵니다.

 

 

 


 

 

signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(4)

signal.signal에 첫 번째 인자로 signal.SIGALRM을 넘겨 alarm을 사용하는 것을 알립니다.

두 번째 인자로 위에서 정의한 alarm_handler를 넘겨 마무리합니다.

signal.alarm(4)는 4초 뒤에 alarm_handler 내부의 로직을 실행하겠다 라는 의미입니다.

 

 

 

 


 

 

 

try:
    loop_for_n_seconds(6)
except TimeOutException as e:
    print(e)

try 안에서 위에서 정의한 loop_for_n_seconds에 6을 넘겨 6초동안 프로세스를 점유하여 실행합니다.

만약 loop_n_seconds가 실행하는 동안 위에서 실행한 signal.alarm(4)이 먼저 발생하게 된다면

내부에 정의한 raise TimeOutException이 발생하게 될 것입니다.

TimeOutException이 발생하면 except문이 해당 exception을 잡아 실행하는 로직입니다.

 

 

 

여기서

timeout을 이용하여 서문에서 이야기한 문제를 해결 할 수 있습니다.

서버 인프라가 timeout을 내게되면 인프라가 문제를 발생시킨 것 또한 클라이언트로 전달할 수 없습니다.

그 전에 내부로직에서 스스로 timeout을 내고 그 exception을 잡아서

직접 그 시점까지 처리한 정보와 함께 시간이 넘어섰다라는 정보를 넘깁니다.

그렇다면 클라이언트는 그것을 알고 이후의 행동을 할 수 있습니다.

 

 

두 가지 더!

 

마지막에서 signal.alarm(0)을 실행해 주지 않으면 alarm_handler내부의 로직은 loop이 끝났어도 alarm에 설정한 시간이 끝나면 실행됩니다.

 

signal모듈은 UNIX 운영체제에서만 실행됩니다.

MacOS에서 실행되는 것을 확인하였고 Ubuntu Linux에서 실행되는 것은 확인하였습니다.

 

 

 

읽어주셔서 감사합니다. :)

 

 

 

다른 글 보기

https://growd.tistory.com/48

 

[Prisma] Prisma(Graphql) 로컬서버 설정 방법(+ docker 설정 방법) - PART 1/2

Prisma를 배울때는 Prisma 공식 홈에서 제공하는 Prisma Cloud를 사용하는것이 간단하고 편리하지만 실제로 개발을 할 때에는 로컬 혹은 자신의 서버에 Prisma 서버를 띄우고 사용해야 합니다. Prisma 공식 문서에..

growd.tistory.com

https://growd.tistory.com/10

 

[Python] Python Django Template에서 slice 하는 방법

Python Django Template에서 slice 하는 방법 데이터 처리를 View에서 한다면 좋겠지만 때로는 template에서 처리해야하는 경우가 있습니다. slice를 해야하는 경우라면 아래와 같이 처리할 수 있습니다. {# index..

growd.tistory.com

https://growd.tistory.com/8

 

[Python] 쉬운 n진법 to 10진법 변환

Python 쉬운 n진법 변환 Python 으로 n진법의 수를 10진법으로 변환한다고 생각해봅시다. 아마 대부분 아래와 비슷한 코드를 생각할 것입니다. # 변환 대상 숫자 target_number = '9983' # n진법의 n base_number..

growd.tistory.com

 

댓글