View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2002-2023, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(time,
   39          [ alarm/3,                    % +Time, :Callable, -Id
   40            alarm/4,                    % +Time, :Callable, -Id, +Options
   41            alarm_at/3,                 % +Time, :Callable, -Id
   42            alarm_at/4,                 % +Time, :Callable, -Id, +Options
   43            remove_alarm/1,             % +Id
   44            install_alarm/1,            % +Id
   45            install_alarm/2,            % +Id, +Time
   46            uninstall_alarm/1,          % +Id
   47            current_alarm/4,            % ?At, ?:Goal, ?Id, ?Status
   48            call_with_time_limit/2,     % +Time, :Callable
   49            call_with_time_limit/3      % +Time, :Callable, +Context
   50          ]).   51:- autoload(library(lists),[member/2]).   52
   53:- set_prolog_flag(generate_debug_info, false).   54
   55:- meta_predicate
   56    call_with_time_limit(+, 0),
   57    call_with_time_limit(+, 0, +),
   58    alarm(+, 0, -),
   59    alarm(+, 0, -, +),
   60    alarm_at(+, 0, -, +),
   61    current_alarm(?, :, ?, ?).   62
   63:- predicate_options(alarm/4, 4,
   64                     [ remove(boolean),
   65                       install(boolean)
   66                     ]).   67
   68
   69/** <module> Time and alarm library
   70
   71The library(time) provides timing and alarm functions. Alarms are
   72thread-specific, i.e., creating an alarm causes the alarm goal to be
   73called in the thread that created it. The predicate current_alarm/4 only
   74reports alarms that are related to the calling thread. If a thread
   75terminates, all remaining alarms are silently removed. Most applications
   76use call_with_time_limit/2.
   77*/
   78
   79%!  alarm(+Time, :Callable, -Id) is det.
   80%!  alarm(+Time, :Callable, -Id, +Options) is det.
   81%
   82%   Set up an alarm to be  signaled   Time  seconds from now. If the
   83%   alarm expires, Callable is called   asynchronously. Callable can
   84%   be used to raise  an  exception   using  throw/1  to  abort some
   85%   execution.
   86%
   87%   Options is a list of Name(Value) options.  Currently defined
   88%   options are:
   89%
   90%           * remove(Bool)
   91%           If =true= (default =false=), remove the alarm-event (as
   92%           remove_alarm/1) after it has been fired.
   93%           * install(Bool)
   94%           If =false= (default =true=) do not install the alarm.
   95%           It must be installed separately using install_alarm/1.
   96
   97%!  alarm_at(+Time, :Callable, -Id) is det.
   98%!  alarm_at(+Time, :Callable, -Id, +Options) is det.
   99%
  100%   As alarm/3 and alarm/4, but schedule   the  alarm at an absolute
  101%   point in time.
  102%
  103%   @see date_time_stamp/2.
  104
  105%!  install_alarm(+Id) is det.
  106%!  install_alarm(+Id, +RelTime) is det.
  107%
  108%   Install an alarm allocated using alarm/4 with the install(false)
  109%   option or de-activated using  uninstall_alarm/1.   With  a given
  110%   RelTime, the alarm  is  scheduled  at   the  RelTime  from  now.
  111%   Otherwise it is scheduled on the   same (absolute) time on which
  112%   is was created.
  113
  114%!  uninstall_alarm(+Id) is det.
  115%
  116%   De-activate an alarm. This does _not_ invalidate Id, but ensures
  117%   that the alarm will not fire. The alarm can be rescheduled to
  118%   the original time using install_alarm/1 or to a new time using
  119%   install_alarm/2.
  120
  121%!  remove_alarm(+Id) is det.
  122%
  123%   Remove an alarm.  If it has not yet been fired, it never will.
  124
  125%!  current_alarm(?Time, :Goal, ?Id, ?Status) is nondet.
  126%
  127%   Enumerate the alarms in the schedule.  Time is the absolute time
  128%   the event is scheduled for (see   also  get_time/1). Goal is the
  129%   goal to execute,  Id  is  the   identifier  and  Status  is  the
  130%   scheduling status. It takes the value   =done=  if the alarm has
  131%   been fired, =next= if the event is   the next to be executed and
  132%   =scheduled= otherwise.
  133
  134:- use_foreign_library(foreign(time)).  135:- public time_debug/1.                 % set debugging
  136
  137%!  call_with_time_limit(+Time, :Goal) is det.
  138%!  call_with_time_limit(+Time, :Goal, +Context) is det.
  139%
  140%   Call Goal, while watching out for a (wall-time) limit. If this limit
  141%   is  exceeded,  the  exception    `time_limit_exceeded`   is  raised.
  142%   call_with_time_limit/3 throws time_limit_exceeded(Context).  Goal is
  143%   called as in once/1.
  144%
  145%   @throws `time_limit_exceeded` (call_with_time_limit/2) or
  146%   time_limit_exceeded(Context) (call_with_time_limit/3).
  147
  148call_with_time_limit(Time, Goal) :-
  149    call_with_time_limit(Time, Goal, '$no_ctx').
  150
  151call_with_time_limit(Time, Goal, Ctx) :-
  152    Time > 0,
  153    !,
  154    setup_call_cleanup(alarm(Time, time_limit_exceeded(Time, Ctx),
  155                             Id, [install(false)]),
  156                       run_alarm_goal(Id, Goal),
  157                       remove_alarm_notrace(Id)).
  158call_with_time_limit(_Time, _Goal, _Ctx) :-
  159    throw(time_limit_exceeded).
  160
  161run_alarm_goal(AlarmID, Goal) :-
  162    install_alarm(AlarmID),
  163    Goal,
  164    !.
  165
  166time_limit_exceeded(_Time, Ctx) :-
  167    (   Ctx == '$no_ctx'
  168    ->  throw(time_limit_exceeded)
  169    ;   throw(time_limit_exceeded(Ctx))
  170    ).
  171
  172current_alarm(Time, Goal, Id, Status) :-
  173    current_alarms(Time, Goal, Id, Status, List),
  174    member(alarm(Time, Goal, Id, Status), List).
  175
  176                 /*******************************
  177                 *        HANDLE MESSAGES       *
  178                 *******************************/
  179
  180:- multifile
  181    prolog:message/3.  182
  183prolog:message(time_limit_exceeded) -->
  184    [ 'Time limit exceeded' ].
  185prolog:message(time_limit_exceeded(Context)) -->
  186    [ 'Time limit exceeded: ~p'-[Context] ].
  187
  188                 /*******************************
  189                 *             ALARM            *
  190                 *******************************/
  191
  192:- multifile sandbox:safe_meta_predicate/1.  193
  194sandbox:safe_meta_predicate(time:call_with_time_limit/2)