Jacek
Jacek

Reputation: 1088

Prolog: critical section, backtracking, error handling

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:

  1. Decorate each step with nb_setval to store a value to indicate the completed steps,
  2. Re-code steps, so they throw exceptions that carry details of the problem.

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

Answers (2)

Jacek
Jacek

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

Jan Wielemaker
Jan Wielemaker

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

Related Questions