luv

Workshop wiki

application-lifecycle.lisp

hal/application-lifecycle.lisp

system luv · 35 definitions · on GitHub

Honest, one-owner application teardown.

in-package#:luv

Named release aggregation.

defstruct
release-failure
:constructor%make-release-failure
namecondition

One named release step which signalled CONDITION.

namecondition
defvar*release-failures*nil"Failures collected by RELEASING in the current release scope."
defvar*releasing-depth*0"Dynamic nesting depth of named release steps."
define-conditionrelease-error
error
failures:initarg:failures:readerrelease-error-failures
:documentation

All named failures from a completed release sequence.

:report
lambda
conditionstream
let
failures
release-error-failurescondition
formatstream"~D release step~:P failed.~:{~2%~A:~% ~A~}"
lengthfailures
mapcar
lambda
failure
list
release-failure-namefailure
release-failure-conditionfailure
failures
define-conditionrelease-warning
warning
failures:initarg:failures:readerrelease-warning-failures
primary-condition:initarg:primary-condition:initformnil:readerrelease-warning-primary-condition
:documentation

Named release failures reported without replacing an active transfer.

:report
lambda
conditionstream
let
failures
release-warning-failurescondition
primary
release-warning-primary-conditioncondition
formatstream

D release step:P failed while unwinding~@[ A].~ :{~2%~A:~% ~A}

lengthfailures
primary
mapcar
lambda
failure
list
release-failure-namefailure
release-failure-conditionfailure
failures
defuncall-releasing

Call function as release step name, containing and recording any error.

unless*collecting-release-failures-p*
error"RELEASING must run within a LUV release scope."
let
handler-case
funcallfunction
error
condition
push
%make-release-failurenamecondition
*release-failures*
nil
defmacroreleasing
name&bodybody

Run body as one named, contained release step.

`
call-releasing,name
lambda
,@body
defunreport-release-warning
failuresprimary-condition

Warn about failures without allowing warning policy to mask an unwind.

whenfailures
handler-case
warn'release-warning:failuresfailures:primary-conditionprimary-condition

A global warning policy is allowed to promote warnings to errors, but cleanup reporting still cannot replace the condition already leaving.

error
reporting-error
format*error-output*"Release failures could not be warned normally: ~A~%"reporting-error
finish-output*error-output*
defuncall-with-release-report

Call function, then signal one release-error for all named failures.

An uncontained error from function remains primary. Any failures recorded before that error are reported as a release-warning while it unwinds.

let
entry-releasing-depth*releasing-depth*
completed-pnil
primary-conditionnil
result-valuesnil
handler-bind
error
lambda
condition
unless
orcompleted-p
>*releasing-depth*entry-releasing-depth
primary-condition
setfprimary-conditioncondition
unwind-protect
progn
setfresult-values
multiple-value-list
funcallfunction
completed-pt
unlesscompleted-p
values-listresult-values
defmacrowith-release-report
&bodybody

Run body and signal one release-error after every named step ran.

`
defuncall-with-release-warnings
function&optionalprimary-condition

Call function and warn once about all of its named release failures.

let
entry-releasing-depth*releasing-depth*
completed-pnil
escaping-conditionprimary-condition
result-valuesnil
handler-bind
error
lambda
condition
unless
orcompleted-p
>*releasing-depth*entry-releasing-depth
escaping-condition
setfescaping-conditioncondition
unwind-protect
progn
setfresult-values
multiple-value-list
funcallfunction
completed-pt
values-listresult-values
defmacrowith-release-warnings
&bodybody

Run body and non-fatally report every named release failure.

`
defuncall-with-release-unwind
body-functioncleanup-function

Call body-function and always run the named steps in cleanup-function.

If the body finishes, cleanup failures become one release-error. If the body leaves by an error or another nonlocal transfer, cleanup failures are reported without replacing that primary transfer. Every cleanup step expressed with releasing is attempted in either case.

check-typebody-functionfunction
check-typecleanup-functionfunction
let
entry-releasing-depth*releasing-depth*
completed-pnil
primary-conditionnil
result-valuesnil
handler-bind
error
lambda
condition
unless
orcompleted-p
>*releasing-depth*entry-releasing-depth
primary-condition
setfprimary-conditioncondition
unwind-protect
progn
setfresult-values
multiple-value-list
funcallbody-function
completed-pt
ifcompleted-p
call-with-release-reportcleanup-function
call-with-release-warningscleanup-functionprimary-condition
values-listresult-values
defmacrounwind-protect-releasing
protected-form&bodycleanup-forms

UNWIND-PROTECT with named, exhaustive, primary-preserving cleanup.

`
call-with-release-unwind
lambda
,protected-form
lambda
,@cleanup-forms

One-owner stop publication.

define-conditionstop-controller-error
error
controller:initarg:controller:readerstop-error-controller
:documentation

Base condition for invalid stop-controller operations.

define-conditionstop-controller-blocking-thread-error
operation:initarg:operation:readerstop-error-operation
:report
lambda
conditionstream
formatstream

Cannot ~A ~A from its nonblocking owner thread; request the ~ stop beside that thread instead.

stop-error-operationcondition
stop-controller-name
stop-error-controllercondition
define-conditionrecursive-stop-error
:report
lambda
conditionstream
formatstream"The teardown owner recursively stopped ~A."
stop-controller-name
stop-error-controllercondition
define-conditionstop-not-started-error
:report
lambda
conditionstream
formatstream"No stop has been requested for ~A."
stop-controller-name
stop-error-controllercondition
define-conditionstop-operation-aborted
:report
lambda
conditionstream
formatstream"The teardown owner left ~A without publishing a result."
stop-controller-name
stop-error-controllercondition
define-conditionapplication-attachment-closed
error
controller:initarg:controller:readerapplication-attachment-closed-controller
attachment:initarg:attachment:readerapplication-attachment-closed-attachment
state:initarg:state:readerapplication-attachment-closed-state
:documentation

An application attachment was offered after terminal teardown began.

:report
lambda
conditionstream
formatstream"Cannot attach ~S to ~A while its stop controller is ~(~A~)."
application-attachment-closed-attachmentcondition
stop-controller-name
application-attachment-closed-controllercondition
application-attachment-closed-statecondition
defclassstop-controller
name:initarg:name:initform"application":readerstop-controller-name
blocking-thread-p:initarg:blocking-thread-p:initform
constantlynil
:readerstop-controller-blocking-thread-p
state:initform:running:accessor%stop-controller-state
owner:initformnil:accessor%stop-controller-owner
result-values:initformnil:accessor%stop-controller-result-values
condition:initformnil:accessor%stop-controller-condition
lock:initform
sb-thread:make-mutex:name"LUV stop controller"
:readerstop-controller-lock
ready:initform
sb-thread:make-waitqueue:name"LUV stop controller ready"
:readerstop-controller-ready
:documentation

A one-shot publication boundary around exactly one teardown execution.

The controller is a separate object owned by an application. One caller changes RUNNING to STOPPING and executes the teardown; every other blocking caller waits for the same result or condition. STOPPED is terminal, so no resource-owning body can run twice.

defunmake-stop-controller
&key
name"application"
blocking-thread-p

Make a one-shot stop controller named name.

blocking-thread-p, when supplied, is a quick predicate which is true on a thread that may request a stop but must never own or wait for one.

whenblocking-thread-p
check-typeblocking-thread-pfunction
make-instance'stop-controller:namename:blocking-thread-p
orblocking-thread-p
constantlynil
defunmake-canvas-stop-controller
canvas&key
name"canvas application"

Make a stop controller whose blocking path rejects canvas's native thread.

make-stop-controller:namename:blocking-thread-p
defunstop-controller-state
controller

Return controller's synchronized RUNNING, STOPPING, or STOPPED state.

sb-thread:with-mutex
stop-controller-lockcontroller
%stop-controller-statecontroller
defunstop-controller-condition
controller

Return controller's published failure condition, or NIL.

sb-thread:with-mutex
stop-controller-lockcontroller
%stop-controller-conditioncontroller
defunstop-controller-result-values
controller

Return a fresh list of controller's published normal values.

sb-thread:with-mutex
stop-controller-lockcontroller
copy-list
%stop-controller-result-valuescontroller
defuncall-with-running-stop-controller
controllerfunction&keyattachmentalready-attached-p

Call quick publication function iff controller is still running.

The controller lock remains held across function, making publication atomic with the RUNNING to STOPPING transition. function and already-attached-p must therefore neither block nor reenter controller. A true already-attached-p is an idempotent success even during teardown and returns attachment without republishing it. Otherwise, once stop has begun, signal application-attachment-closed outside the lock so the caller can release its unpublished attachment without deadlocking lifecycle inspection.

check-typecontrollerstop-controller
whenalready-attached-p
check-typealready-attached-pfunction
let
statenil
called-pnil
result-valuesnil
sb-thread:with-mutex
stop-controller-lockcontroller
setfstate
%stop-controller-statecontroller
cond
eq:runningstate
setfresult-values
multiple-value-list
funcallfunction
called-pt
andalready-attached-p
funcallalready-attached-p
setfresult-values
listattachment
called-pt
unlesscalled-p
error'application-attachment-closed:controllercontroller:attachmentattachment:statestate
values-listresult-values
defunensure-stop-blocking-allowed
controlleroperation
when
funcall
stop-controller-blocking-thread-pcontroller
error'stop-controller-blocking-thread-error:controllercontroller:operationoperation
defunpublish-stop-outcome
controllerresult-valuescondition
sb-thread:with-mutex
stop-controller-lockcontroller
setf
%stop-controller-result-valuescontroller
result-values
%stop-controller-conditioncontroller
condition
%stop-controller-ownercontroller
nil
%stop-controller-statecontroller
:stopped
sb-thread:condition-broadcast
stop-controller-readycontroller
values
defunreturn-stop-outcome
result-valuescondition
ifcondition
errorcondition
values-listresult-values
defunrun-stop-owner
controllerfunction
let
result-valuesnil
conditionnil
completed-pnil
unwind-protect
handler-case
progn
setfresult-values
multiple-value-list
funcallfunction
completed-pt
error
failure
setfconditionfailurecompleted-pt
unlesscompleted-p
setfcondition
make-condition'stop-operation-aborted:controllercontroller
publish-stop-outcomecontrollerresult-valuescondition
return-stop-outcomeresult-valuescondition
defuncall-with-stop-controller
controllerfunction

Run function as controller's sole teardown, or observe its published result.

A concurrent caller waits while the owner runs. A later caller receives the same values or signals the same condition without running function again.

check-typecontrollerstop-controller
let
owner-pnil
result-valuesnil
conditionnil
sb-thread:with-mutex
stop-controller-lockcontroller
loop
case
%stop-controller-statecontroller
:running
setf
%stop-controller-statecontroller
:stopping
%stop-controller-ownercontroller
sb-thread:*current-thread*
owner-pt
:stopping
when
eq
%stop-controller-ownercontroller
sb-thread:*current-thread*
error'recursive-stop-error:controllercontroller
ensure-stop-blocking-allowedcontroller:wait-for-stop
sb-thread:condition-wait
stop-controller-readycontroller
stop-controller-lockcontroller
:stopped
setfresult-values
copy-list
%stop-controller-result-valuescontroller
condition
%stop-controller-conditioncontroller
otherwise
error"Invalid state ~S in ~A."
%stop-controller-statecontroller
stop-controller-namecontroller
ifowner-p
return-stop-outcomeresult-valuescondition
defunwait-for-controlled-stop
controller

Wait for controller's existing owner and return its published outcome.

check-typecontrollerstop-controller
let
result-valuesnil
conditionnil
sb-thread:with-mutex
stop-controller-lockcontroller
loop
case
%stop-controller-statecontroller
:running
error'stop-not-started-error:controllercontroller
:stopping
when
eq
%stop-controller-ownercontroller
sb-thread:*current-thread*
error'recursive-stop-error:controllercontroller
ensure-stop-blocking-allowedcontroller:wait-for-stop
sb-thread:condition-wait
stop-controller-readycontroller
stop-controller-lockcontroller
:stopped
setfresult-values
copy-list
%stop-controller-result-valuescontroller
condition
%stop-controller-conditioncontroller
return-stop-outcomeresult-valuescondition
defunrequest-controlled-stop
controllerfunction&keythread-name

Request function as controller's teardown on one new worker thread.

Return true and the worker when this call reserved ownership, or NIL and NIL when a stop was already requested or published. The worker contains any failure after publishing it; wait-for-controlled-stop and synchronous callers receive that exact condition.

check-typecontrollerstop-controller
let
ticket
list:reserved-stop-owner
begin-pnil
sb-thread:with-mutex
stop-controller-lockcontroller
when
eq:running
%stop-controller-statecontroller
setf
%stop-controller-statecontroller
:stopping
%stop-controller-ownercontroller
ticket
begin-pt
if
notbegin-p
valuesnilnil
handler-case
let
thread
sb-thread:make-thread
lambda
sb-thread:with-mutex
stop-controller-lockcontroller
when
eqticket
%stop-controller-ownercontroller
setf
%stop-controller-ownercontroller
sb-thread:*current-thread*

The published condition is the asynchronous API; an unhandled worker error would only enter a debugger.

handler-case
error
nil
:name
orthread-name
formatnil"stop ~A"
stop-controller-namecontroller
valuestthread
error
condition
publish-stop-outcomecontrollernilcondition
errorcondition