Reputation: 1088
I'm trying to write a critical section guarded by a mutex in SWI-Prolog and have been looking at using setup_call_cleanup/3
and setup_call_catcher_cleanup/4
.
The problem I have is that my Goal is a sequence of operations of which any may fail and that means the system backtracks to the start of setup_call_cleanup
and calls Cleanup. Unfortunately, with backtracking I'm not able to report the error appropriately. To illustrate my issue let's consider this simple example:
setup_call_cleanup(
mutex_lock(mtx),
( Step1 = true, Step2 = true, Step3 = true ),
( mutex_unlock(mtx), writeln([Step1, Step2, Step3]) ).
and compare it with the following:
setup_call_cleanup(
mutex_lock(mtx),
( Step1 = true, Step2 = true, fail, Step3 = true ),
( mutex_unlock(mtx), writeln([Step1, Step2, Step3]) ).
In the first case all is ok -- I can see all steps done. But in the second case I'm not able to see that Step1
and Step2
has been carried out. I'd like to see it because they may have external side effects which backtracking cannot undo. Also, I don't want to include error handling within the Goal to make the critical section as lean and fast as possible.
I have two ideas:
nb_setval
to store a value to indicate the completed steps, The former will make code rather bloated, whereas the latter seems too heavyweight for my needs. Is there anything like setup_nb_call_cleanup
?
Upvotes: 3
Views: 185
Reputation: 1088
Thank you Jan for the inspiration; very useful. I ended up coding a similar step_by_step
rule:
step_by_step(Goal, Steps, Error) :-
step_by_step_(Goal, 0, Steps, Error).
step_by_step_((A, B), InStep, OutStep, Error) :-
!,
step_by_step_(A, InStep, OutStep1, Error),
( var(Error) ->
step_by_step_(B, OutStep1, OutStep, Error)
;
OutStep = InStep
).
step_by_step_(Goal, InStep, OutStep, Error) :-
( catch(Goal, Ex, (Error = exception(Ex), OutStep = InStep)) *->
(OutStep is InStep + 1 ; true), !
;
Error = false(Goal),
OutStep = InStep
).
I'm not happy with (OutStep is InStep + 1 ; true), !
but wasn't able to find a better way.
Anyway, the rule gives me what I want:
-- if all goes ok, it just runs all steps in sequence:
?- step_by_step((Step1 = true, Step2 = true, Step3 = true), Steps, Error).
Step1 = Step2, Step2 = Step3, Step3 = true,
Steps = 3.
-- if one of the step fails or throws an exception, it returns the number of steps completed successfully and the failed goal:
?- step_by_step((Step1 = true, Step2 = true, fail, Step3 = true), Steps, Error).
Step1 = Step2, Step2 = true,
Steps = 2,
Error = false(fail).
or the exception:
?- step_by_step((Step1 = true, Step2 = true, throw(bomb), Step3 = true), Steps, Error).
Step1 = Step2, Step2 = true,
Steps = 2,
Error = exception(bomb).
Upvotes: 0
Reputation: 1710
The trick, I think, is to run the goals one by one, guarded for errors and failure and return step that failed. A good start is
until_failure((A,B), Result) :-
!,
until_failure(A, Result),
( var(Result)
-> until_failure(B, Result)
; true
).
until_failure(G, Result) :-
( catch(G, Result, true)
*-> true
; Result = false(G)
).
Now you can run e.g.,
?- until_failure((Step1 = true,
Step2 = true,
fail,
Step3 = true), Result),
writeln([Step1, Step2, Step3]).
[true, true, _5742]
Result = false(fail)
See http://swish.swi-prolog.org/p/ReQWsvCg.swinb. SWISH doesn't allow for
handling mutexes, but you can easily wrap this inside with_mutex/2
. The details depend notably on how you want to handle non-determinism.
Upvotes: 2