중단
중단 (Breaks)
브레이크(break)는 비동기 예외로, 보통 사용자가 제어하는 외부 자극이나 break-thread 프로시저를 통해 발생해요. 예를 들어 사용자가 터미널에서 Ctrl-C를 누르면 브레이크가 발생하죠. 이 문서에서는 브레이크가 어떻게 동작하고, 어떻게 제어하는지 설명할게요.
출처: Racket Reference
본문
10.6 중단
브레이크는 비동기 예외(asynchronous exception)로, 대개 사용자가 제어하는 외부 소스나 break-thread 프로시저를 통해 발생합니다. 예를 들어 사용자가 터미널에서 Ctrl-C를 입력해 브레이크를 발생시킬 수 있어요. 일부 플랫폼에서는 Racket 프로세스가 SIGINT, SIGHUP, 또는 SIGTERM을 받을 수 있는데, 뒤의 두 개는 각각 exn:break:hang-up과 exn:break:terminate로 반영되는 hang-up 브레이크와 terminate 브레이크에 해당합니다. 여러 브레이크가 단일 예외로 합쳐질 수 있고, 서로 다른 종류의 여러 브레이크도 하나의 "가장 강한" 브레이크로 합쳐질 수 있어요. 이때 terminate 브레이크는 hang-up 브레이크보다 강하고, hang-up 브레이크는 인터럽트 브레이크보다 강합니다.
브레이크 예외는 브레이크가 활성화되어 있을 때에만 스레드에서 발생할 수 있습니다. 브레이크가 감지되고 활성화되어 있으면, 그 후 어느 시점에 exn:break(또는 exn:break:hang-up이나 exn:break:terminate) 예외가 해당 스레드에서 발생합니다. break-thread가 호출될 때 브레이킹이 비활성화되어 있으면, 해당 스레드에서 브레이킹이 다시 활성화될 때까지 브레이크는 보류(suspended)됩니다. 스레드에 보류된 브레이크가 있는 동안 추가 브레이크는 무시됩니다.
브레이크는 break-enabled 매개변수-유사(parameter-like) 프로시저와, parameterize와 유사한 parameterize-break 폼을 통해 활성화됩니다. break-enabled 프로시저는 parameterize와 함께 쓸 매개변수를 나타내지 않습니다. 그 이유는 스레드의 브레이크 활성 상태를 바꾸려면 브레이크를 명시적으로 검사해야 하는데, 이 검사가 parameterize 표현식 본문의 꼬리 평가와 호환되지 않기 때문이에요.
semaphore-wait/enable-break 같은 일부 프로시저는 블로킹 동작을 수행하는 동안 브레이크를 임시로 활성화합니다. 스레드에 브레이크가 활성화되어 있고, 스레드에 브레이크가 트리거되었지만 아직 exn:break 예외로 전달되지 않았다면, 그 브레이크는 스레드에서 브레이크가 비활성화되기 전에 반드시 전달됨이 보장됩니다. exn:break 예외의 타이밍은 그 외의 어떤 방식으로도 보장되지 않아요.
with-handlers 서술어나 핸들러, 예외 핸들러, 오류 표시 핸들러, 오류 탈출 핸들러, 오류 값 변환 핸들러, 또는 dynamic-wind의 pre-thunk나 post-thunk를 호출하기 전에, 그 호출은 브레이크를 비활성화하도록 parameterize-breaked 처리됩니다. 또한 예외와 관련된 핸들러 사이의 전이 동안, dynamic-wind의 pre-thunk와 post-thunk 사이의 전이 동안, 그리고 continuation 점프를 위한 다른 전이 동안에도 브레이크는 비활성화됩니다. 예를 들어 continuation이 호출될 때 브레이크가 비활성화되어 있고 대상 continuation에서도 브레이크가 비활성화되어 있다면, 관련된 dynamic-wind pre-thunk나 post-thunk가 명시적으로 브레이크를 활성화하지 않는 한, 호출 시점부터 대상 continuation이 실행될 때까지 브레이크는 비활성화 상태로 유지됩니다.
중첩 스레드(참고 call-in-nested-thread)에 블록된 스레드에 브레이크가 트리거되고, 그 블록된 스레드에서 브레이크가 활성화되어 있다면, 브레이크는 중첩 스레드로 전달되는 방식으로 암묵적으로 처리됩니다.
브레이크가 활성화되어 있을 때는 실행 중 어느 지점에서나 발생할 수 있어서, 특정 구현 작업이 미묘해집니다. 예를 들어 브레이크가 활성화된 채로 다음 코드를 실행한다고 가정해 봅시다.
(with-handlers ([exn:break? (lambda (x) (void))])
(semaphore-wait s))
이 경우 #<void> 결과가 "세마포어가 감소되었다" 또는 "브레이크를 받았다" 중 하나만을 의미한다고 단정할 수 없습니다. 둘 다 일어날 수 있어요. 즉 브레이크가 세마포어를 성공적으로 감소시킨 뒤, semaphore-wait가 #<void> 결과를 반환하기 전에 발생할 수 있습니다. 브레이크 예외는 세마포어나 다른 내장 구조를 손상시키지 않지만, 많은 내장 프로시저(semaphore-wait 포함)는 브레이크에 의해 중단될 수 있는 내부 하위 표현식을 담고 있습니다.
일반적으로 semaphore-wait만 사용해서는 "세마포어가 감소되거나 예외가 발생하되, 둘 다는 아니다"라는 보장을 구현하는 것이 불가능합니다. 그래서 Racket은 그런 배타적 보장을 구현할 수 있게 해 주는 semaphore-wait/enable-break(세마포어 참고)를 제공합니다:
(parameterize-break #f
(with-handlers ([exn:break? (lambda (x) (void))])
(semaphore-wait/enable-break s)))
위 표현식에서 브레이크는 브레이크가 비활성화될 때까지 어느 지점에서나 발생할 수 있으며, 그 경우 브레이크 예외는 둘러싼 예외 핸들러로 전파됩니다. 그렇지 않으면 브레이크는 오직 semaphore-wait/enable-break 안에서만 발생할 수 있고, 이는 브레이크 예외가 발생하면 세마포어가 감소되지 않았을 것임을 보장합니다.
블로킹 포트 연산에 대해서도 비슷한 구현 패턴을 허용하기 위해, Racket은 read-bytes-avail!/enable-break, write-bytes-avail/enable-break 및 다른 프로시저들을 제공합니다.
break-enabled
(break-enabled) → boolean?
(break-enabled on?) → void?
on? : any/c
현재 스레드의 브레이크 활성 상태를 얻거나 설정합니다. on?가 주어지지 않으면, 브레이크가 현재 활성화되어 있다면 #t를, 그렇지 않으면 #f를 반환합니다. on?가 #f로 주어지면 브레이크가 비활성화되고, on?가 참 값이면 브레이크가 활성화됩니다.
parameterize-break
(parameterize-break boolean-expr body ...+)
boolean-expr을 평가해 body들을 순서대로 평가하는 동안 브레이크를 처음부터 활성화할지 결정합니다. parameterize-break 표현식의 결과는 마지막 expr의 결과입니다.
parameterize와 마찬가지로, continuation의 브레이크 활성 상태를 담는 새 스레드 셀(thread cell)이 할당되고, continuation 안에서의 break-enabled 호출은 그 새 셀에 접근하거나 수정합니다. 매개변수(parameter)와 달리, break-enabled를 통한 브레이크 설정 변경은 새 스레드에 상속되지 않습니다(즉, 스레드 셀이 보존되지 않습니다).
current-break-parameterization
(current-break-parameterization) → break-parameterization?
(current-parameterization)(매개변수 참고)에 해당하는 것과 유사합니다. 현재 continuation의 브레이크 활성 상태를 담는 브레이크 매개변수화(break parameterization, 사실상 스레드 셀)를 반환합니다.
call-with-break-parameterization
(call-with-break-parameterization break-param thunk) → any
break-param : break-parameterization?
thunk : (-> any)
(call-with-parameterization parameterization thunk)(매개변수 참고)에 해당하는 것과 유사합니다. break-param에 브레이크 활성 상태가 있는 continuation에서 thunk를 호출합니다. thunk는 call-with-break-parameterization 호출에 대해 꼬리 위치에서 호출되지 않습니다.
break-parameterization?
(break-parameterization? v) → boolean?
v : any/c
v가 current-break-parameterization이 만들어 낸 브레이크 매개변수화이면 #t를, 그렇지 않으면 #f를 반환합니다.
Added in version 6.1.1.8 of package base.