#lang scribble/doc
@(require "mz.ss")

@title[#:tag "breakhandler"]{Breaks}

@section-index["threads" "breaking"]

A @deftech{break} is an asynchronous exception, usually triggered
through an external source controlled by the user, or through the
@racket[break-thread] procedure. A break exception can only occur in a
thread while breaks are enabled. When a break is detected and enabled,
the @exnraise[exn:break] in the thread sometime afterward; if breaking
is disabled when @racket[break-thread] is called, the break is
suspended until breaking is again enabled for the thread. While a
thread has a suspended break, additional breaks are ignored.

Breaks are enabled through the @racket[break-enabled] parameter-like
procedure, and through the @racket[parameterize-break] form, which is
analogous to @racket[parameterize]. The @racket[break-enabled]
procedure does not represent a parameter to be used with
@racket[parameterize], because changing the break-enabled state of a
thread requires an explicit check for breaks, and this check is
incompatible with the tail evaluation of a @racket[parameterize]
expression's body.

Certain procedures, such as @racket[semaphore-wait/enable-break],
enable breaks temporarily while performing a blocking action. If
breaks are enabled for a thread, and if a break is triggered for the
thread but not yet delivered as an @racket[exn:break] exception, then
the break is guaranteed to be delivered before breaks can be disabled
in the thread. The timing of @racket[exn:break] exceptions is not
guaranteed in any other way.

Before calling a @racket[with-handlers] predicate or handler, an
exception handler, an error display handler, an error escape handler,
an error value conversion handler, or a @racket[pre-thunk] or
@racket[post-thunk] for a @racket[dynamic-wind], the call is
@racket[parameterize-break]ed to disable breaks. Furthermore, breaks
are disabled during the transitions among handlers related to
exceptions, during the transitions between @racket[pre-thunk]s and
@racket[post-thunk]s for @racket[dynamic-wind], and during other
transitions for a continuation jump. For example, if breaks are
disabled when a continuation is invoked, and if breaks are also
disabled in the target continuation, then breaks will remain disabled
until from the time of the invocation until the target continuation
executes unless a relevant @racket[dynamic-wind] @racket[pre-thunk] or
@racket[post-thunk] explicitly enables breaks.

If a break is triggered for a thread that is blocked on a nested
thread (see @racket[call-in-nested-thread]), and if breaks are enabled
in the blocked thread, the break is implicitly handled by transferring
it to the nested thread.

When breaks are enabled, they can occur at any point within execution,
which makes certain implementation tasks subtle. For example, assuming
breaks are enabled when the following code is executed,

@racketblock[
(with-handlers ([exn:break? (lambda (x) (void))])
  (semaphore-wait s))
]

then it is @italic{not} the case that a @|void-const| result means the
semaphore was decremented or a break was received, exclusively. It is
possible that @italic{both} occur: the break may occur after the
semaphore is successfully decremented but before a @|void-const|
result is returned by @racket[semaphore-wait]. A break exception will
never damage a semaphore, or any other built-in construct, but many
built-in procedures (including @racket[semaphore-wait]) contain
internal sub-expressions that can be interrupted by a break.

In general, it is impossible using only @racket[semaphore-wait] to
implement the guarantee that either the semaphore is decremented or an
exception is raised, but not both.  Racket therefore supplies
@racket[semaphore-wait/enable-break] (see @secref["semaphore"]),
which does permit the implementation of such an exclusive guarantee:

@racketblock[
(parameterize-break #f
  (with-handlers ([exn:break? (lambda (x) (void))])
    (semaphore-wait/enable-break s)))
]

In the above expression, a break can occur at any point until breaks
are disabled, in which case a break exception is propagated to the
enclosing exception handler. Otherwise, the break can only occur
within @racket[semaphore-wait/enable-break], which guarantees that if
a break exception is raised, the semaphore will not have been
decremented.

To allow similar implementation patterns over blocking port
operations, Racket provides @racket[read-bytes-avail!/enable-break],
@racket[write-bytes-avail/enable-break], and other procedures.


@;------------------------------------------------------------------------

@defproc*[([(break-enabled) boolean?]
           [(break-enabled [on? any/c]) void?])]{

Gets or sets the break enabled state of the current thread. If
@racket[on?] is not supplied, the result is @racket[#t] if breaks are
currently enabled, @racket[#f] otherwise.  If @racket[on?] is supplied
as @racket[#f], breaks are disabled, and if @racket[on?] is a true
value, breaks are enabled.}

@defform[(parameterize-break boolean-expr body ...+)]{Evaluates
@racket[boolean-expr] to determine whether breaks are initially
enabled in while evaluating the @racket[body]s in sequence. The result
of the @racket[parameter-break] expression is the result of the last
@racket[expr].

Like @racket[parameterize] (see @secref["parameters"]), a fresh
@tech{thread cell} (see @secref["threadcells"]) is allocated to
hold the break-enabled state of the continuation, and calls to
@racket[break-enabled] within the continuation access or modify the
new cell. Unlike parameters, the break setting is not inherited by new
threads.}
 
@defproc[(current-break-parameterization) break-parameterization?]{
Analogous to @racket[(current-parameterization)] (see
@secref["parameters"]); it returns a break-parameterization
(effectively a thread cell) that holds the current continuation's
break-enable state.}

@defproc[(call-with-break-parameterization 
                [break-param break-parameterization?]
                [thunk (-> any)]) 
               any]{
Analogous to @racket[(call-with-parameterization parameterization
thunk)] (see @secref["parameters"]), calls @racket[thunk] in a
continuation whose break-enabled state is in @racket[break-param]. The
@racket[thunk] is @italic{not} called in tail position with respect to
the @racket[call-with-break-parameterization] call.}
