Writing Plans

with-policy

Policies can be used to define generic monitoring behavior that should run concurrently besides main functionality code. Such a policy might be monitoring

  • the position of an object inside a robot gripper to see whether it is moving out of the gripper (losing the object)
  • the position of an object in the real world, in order to keep the robot's head pointing towards it
  • the collision of a robot joint with objects in the real world
  • generic events that must definitely interrupt a certain piece of code, but are too specialized to be implemented directly in the monitored code itself

A policy consists of multiple parts:

  • A name (to identify it)
  • A description string (to make its purpose clear)
  • A parameter list (which can be used to specialize a generic policy for a situation at hand, without generating a new policy every time the situation changes slightly)
  • Code blocks that need to be evaluated during monitoring

Usage example

An example of how to define a policy is shown here. The policy accepts two custom parameters, one defining a maximum number and one a match number. During execution of the main body code (which is really just a loop with an output here), it checks a randomly generated number (ranging from 0 to max-num) against the match number match-num. If this is the case (and it will be, eventually), it interrupts (and ends) the main body code and executes the :recover code, followed by the :clean-up code (both just outputting something here).

(define-policy my-policy (max-num match-num)
  "This is an example policy."
  (:init (format t "Initializing policy~%")
         t)
  (:check (format t "Checking if random number from 0
                     to ~a equals ~a~%" max-num match-num)
          (let ((rnd (random max-num)))
            (format t "Got number ~a~%" rnd)
            (cond ((eql rnd match-num)
                   (format t "Match~%")
                   t)
                  (t (sleep 1)))))
  (:recover (format t "Running recovery mechanisms~%"))
  (:clean-up (format t "Running clean-up~%")))

The calling code for using the policy uses with-named-policy to refer to the name as specified while defining the policy. The second parameter is a list of parameter values for customizing the policy instance. The rest of the code should be pretty self explanatory.

(top-level
  (with-named-policy 'my-policy (10 5)
    (loop do (format t "Main loop cycle.~%")
             (sleep 2))))

Exception handling

When policies are used, multiple failures can be signalled. The most meaningful of those are

  • policy-not-found: Signalled when a named policy is used that was not defined before.
  • policy-init-failed: Signalled when initialization of a policy went wrong (i.e. :init returned nil).
  • policy-check-condition-met: The :check condition of the policy returned a non-nil value, :recover was executed and the body code was interrupted before it could complete execution.

If one wants to monitor the triggering of a policy's :check condition, this can be achieved like this:

(top-level
  (with-failure-handling
      ((policy-check-condition-met (f)
         (declare (ignore f))is available that the given
         (do-custom-handling-here)
         (retry))) ;; Or whatever seems appropriate in your use-case e.g. (return)
    (with-named-policy 'my-policy (10 5)
      (loop do (format t "Main loop cycle.~%")
               (sleep 2)))))

Using multiple policies at once

When multiple policies are to be used (either a mix of different policies, or the same policy multiple times, each with different parameters), two helpful macros can be used: with-policies and with-named-policies. Both of these take lists of policies, together with their respective instantiation parameters, as arguments.

When policy instances by the names my-policy-object and my-other-policy-object should be used, the following code snippet reflects this behaviour. The policy my-policy-object takes two ints as parameters, while is available that the givenmy-other-policy-object takes a string as an argument.

(with-policies
    ((my-policy-object (3 1))
     (my-policy-object (100 4))
     (my-other-policy-object ("Test")))
  (body-code))

In this example, the resulting code will be equivalent to the following:

(with-policy my-policy-object (3 1)
  (with-policy my-policy-object (100 4)
    (with-policy my-other-policy-object ("Test")
      (body-code))))

The same princple applies to with-named-policies, with the only difference being that it does not take policy instances, but policy name symbols as parameters:

(with-named-policies
    (('my-policy (3 1))
     ('my-policy (100 4))
     ('my-other-policy ("Test")))
  (body-code))

This results in the same behavior as:

(with-named-policy 'my-policy (3 1)
  (with-named-policy 'my-policy (100 4)
    (with-named-policy 'my-other-policy ("Test")
      (body-code))))

Built-in Policies

timeout-policy

When a piece of code only has a limited maximum amount of time for execution (and must be aborted after that duration), the timeout-policy comes in handy.

Use it like this:

(with-policy cpl:timeout-policy (5.0) ; Timeout after 5.0 seconds (fractions may be used)
  (body-code-goes-here))

And for catching the check condition when the timeout actually happens:

(with-failure-handling
    ((policy-check-condition-met (f)
      (declare (ignore f))
      (handle-error-here-and-maybe-retry)))
  (with-policy cpl:timeout-policy (5.0)
    (body-code-goes-here)))

The timeout-policy stops the given body code after a given amount of time (in seconds) if it hasn't finished by then. This helps to add a timeout functionality to functions that do not inherently support a timeout mechanism (blocking function calls).