ISO/IEC JTC1 SC22 WG17 N215
Draft proposal for setup_call_cleanup/3

Ulrich Neumerkel, 2009-10-29 (Version history)
This draft proposal is based on 8.15.5 of N208 2008-11-17. Earlier version: N211 2009-07-02. It is intended to be added as 7.8.11. References refer to 13211-1:1995. Parts needing improvements are underlined.

Contributors

Bart Demoen (Belgium). Paulo Moura (Portugal). Jonathan Hodgson (USA). Richard O'Keefe (New Zealand). Feliks Kluzniak (USA). Jeffrey Rosenwald (USA). Markus Triska (Austria). Jan Wielemaker (The Netherlands).

7.8.11 setup_call_cleanup/3

This control construct allows managing resources related to the execution of a goal. It provides protected setup and performs cleanup as soon as the goal has completed execution.
NOTE — A built-in predicate call_cleanup/2 with similar functionality is implemented in many existing processors. It is often used to free temporary resources. In the presence of interrupts (7.12 note c), call_cleanup/2 can cause leakage of resources: A processor receiving interrupts between resource allocation and call_cleanup/2 is unable to free the resource in a timely manner. To overcome this problem, setup_call_cleanup/3 protects resource allocation from interrupts.

7.8.11.1 Description

setup_call_cleanup(S, G, C) is true iff once(S), call(G) is true.

Procedurally, the control construct shall be executed as follows:

a) once(S) is executed while being protected from interrupts:
  1. either once(S) is executed entirely and the cleanup handler C is installed upon success;
  2. or setup_call_cleanup/3 is interrupted by an implementation dependent interrupt (7.12 note c) without leaving an observable effect.
b) After the cleanup handler is installed, call(G) is called.
c) The cleanup handler is called exactly once; no later than upon failure of G. Earlier moments are:
  1. If G is true or false, C is called at an implementation dependent moment after the last solution and after the last observable effect of G.
  2. When G or the continuation of G is interrupted by a call of throw/1 (7.8.9 a, b), whose corresponding call of catch/3 is above G.
    After executing the cleanup C, throw/1 continues to search the corresponding call of catch/3, regardless of the outcome of C. Thus, if C is interrupted by another call of throw/1, that throw/1 is lost.
  3. When the continuation of G executes a cut explicitly or implicitly that is associated with the cutparent of G (7.7.2, 7.8.4.1 Note 1b).
    After the cut has been executed, C is called in the place of the cut. A cut is performed implicitly for (->)/2 - if-then (7.8.7), (;)/2 - if-then-else (7.8.8), (\+)/1 (8.15.1), once/1 (8.15.2).
d) C is called as once(C). Failure of C is ignored. An explicit or implicit throw/1 is ignored for c2; otherwise (c1 or c3), it is passed to the corresponding call of catch/3.
Several cleanup goals triggered simultaneously are executed in reversed order of installation.

C shares bindings and variables with S, with G and with the continuation. The bindings up to and including S are always present. No further bindings are present when C is executed at c2 and at the latest moment of c1. At c2, and at the earliest moment of c1, all bindings are present at the time of calling the cut. All other cases of c1 are implementation dependent.

A processor may restrict execution of C in an implementation defined manner. For example, a processor may restrict the handling of interrupts within C or limit resource consumption.

NOTES
1 Existing processors locate c1 either when the last solution is found, or upon failure. The precise moment is implementation dependent due to the varying ability of Prolog processors to detect determinism. For example, a processor may execute call((call(G);fail)) as call(G) and vice versa.
2 For nonterminating goals like repeat (8.15.3) only c2 and c3 can trigger the cleanup. And if a nonterminating goal does not find a solution, only c2 can trigger.
3 A throw/1 of G has priority over one of C (c2). This is an advantage when recovering errors, like resource errors (7.12.2 h), where the error of C is often a consequence of the error of G.

7.8.11.2 Template and modes

setup_call_cleanup(?term, ?term, ?term)
NOTE — The arguments should eventually be +callable_term. However, neither type nor mode is checked when this control construct is executed (8.1.2.2).

7.8.11.3 Errors

a) S is a callable term that is not permitted due to an implementation defined restriction
representation_error(setup_goal).
b) S finds a solution, but C is a variable
instantiation_error. Goal G is not executed.
c) S finds a solution, but C is neither a variable nor a callable term
type_error(callable, C). Goal G is not executed.
NOTE — Further errors due to S and G are already implied by the present definition.

7.8.11.4 Examples

In the following, write/1 uses the standard operator table (6.3.4.4, table 7). Unique variables are written as _ which is one possible way to realize 7.10.5 a. The different outcomes (labeled either/or) are due to the implementation dependence in c1.
setup_call_cleanup(fail, _, _).
   Fails.  Neither the goal nor the cleanup is executed.

setup_call_cleanup(true, throw(unthrown),_).
   Instantiation error.

setup_call_cleanup(true, true, ( true ; throw(x) )).
   Succeeds. No system error.

setup_call_cleanup(true, X = 1, X = 2).
   Succeeds, unifying X = 1.

setup_call_cleanup(true, true, X = 2).
   Either: Succeeds, unifying X = 2.
   Or: Succeeds.

setup_call_cleanup(true, X=true,X).
   Instantiation error.

setup_call_cleanup(X=throw(ex), true, X).
   Either: System error due to uncaught ex.
   Or: Succeeds. System error on backtracking.

setup_call_cleanup(true, true, fail).
   Succeeds.

setup_call_cleanup(S=1, G=2, C=3).
   Either: Succeeds, unifying S = 1, G = 2, C = 3.
   Or:  Succeeds, unifying S = 1, G = 2.

setup_call_cleanup((S=1;S=2), G=3, C=4).
   Either: Succeeds, unifying
           S = 1, G = 3, C = 4.
   Or: Succeeds, unifying S = 1, G = 3.

setup_call_cleanup(S=1,G=2,write(S+G)).
   Succeeds, unifying S = 1, G = 2.
   Either: outputs '1+2'
   Or: outputs on backtracking '1+_' prior to failure.
   Or (?): outputs on backtracking '1+2' prior to failure.

setup_call_cleanup(S=1,(G=2;G=3),write(S+G)).
   Succeeds, unifying S = 1, G = 2.
   On backtracking, succeeds unifying S = 1 and G = 3.
   Either: outputs '1+3'
   Or: on backtracking outputs '1+_' prior to failing
   Or (?): on backtracking outputs '1+3' prior to failing.

setup_call_cleanup(S=1,G=2,write(S+G>A+B)), A=3, B=4.
   Succeeds, unifying S=1,G=2,A=3,B=4.
   Either:  Outputs one of the following before succeeding
   1+2>_+_.   Disputable: 1+2>3+_.   1+2>3+4.   1+2>_+4.
   Or: outputs one of the above outputs on backtracking
   prior to failing.

setup_call_cleanup(S=1,(G=2;G=3,throw(x)),write(S+G)).
   Succeeds, unifying S = 1, G = 2.
   On backtracking, outputs '1+_' prior
   to system error due to uncaught x.

setup_call_cleanup(open(f,read,S),read(S,X),close(S)).
   Opens file f for reading, reads a term and closes the file.
   Succeeds, unifying S with the term read.
   The file is closed, either immediately, or on backtracking,
   and even if there is an error in read.

Events in the continuation

setup_call_cleanup(S=1,(G=2;G=3),write(S+G>B)), B=4, !.
   Outputs '1+2>4'. Succeeds, unifying S = 1, G = 2, B = b.

setup_call_cleanup(S=1,G=2,write(S+G>B)),B=3,!.
   Either:  Outputs '1+2>3'. Succeeds, unifying
      S = 1, G = 2, B = 3.
   Or: Outputs  '1+2>_'. Succeeds, unifying
      S = 1, G = 2, B = 3.

setup_call_cleanup(S=1,(G=2;fail),write(S+G>B)), B=3, !.
   Same as above.

setup_call_cleanup(S=1,(G=2;S=2),write(S+G>B)), B=3, !.
   Same as above.

setup_call_cleanup(S=1,(G=2;G=3), write(S+G>B)), B=4, throw(x).
   Outputs '1+_>_'. system_error due to uncaught x.

setup_call_cleanup(S=1,(G=2;G=3), write(S+G>B)), B=4, !, throw(x).
   Outputs '1+2>4'. system_error due to uncaught x.

setup_call_cleanup(true, (X=1;X=2), write(a)), setup_call_cleanup(true,(Y=1;Y=2),write(b)), !.
   Outputs 'ba'.  Succeeds, unifying X = 1, Y = 1.

Multiple exceptions

catch(setup_call_cleanup(true,throw(goal),throw(cl)), Pat, true).
   Succeeds unifying Pat = goal.

catch(( setup_call_cleanup(true,(G=1;G=2),throw(cl)), throw(cont)), Pat, true).
   Succeeds unifying Pat = cont.

Implementation status

Systems with setup_call_cleanup/3

SWI: almost fully compliant since Autumn 2007. Open: bindings (in d) for c2 different.
YAP-6: almost fully compliant since June 2009. Open: errors b and c.
B-Prolog: very close. Open: bindings (in d) for c2 different. Different priorities in c2.

Systems with call_cleanup/2

SICStus: original implementation 1997. c2 different.
XSB: sometimes the cleanup is not executed for c2 and c3 (as of 2009-07)
untested: CxProlog, Qu-Prolog, K-Prolog

Validated HTML