View source with raw 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)  1999-2024, University of Amsterdam
    7                              VU University Amsterdam
    8                              SWI-Prolog Solutions b.v.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(thread_util,
   37          [ threads/0,                  % List available threads
   38            join_threads/0,             % Join all terminated threads
   39            with_stopped_threads/2,     % :Goal, +Options
   40            thread_has_console/0,       % True if thread has a console
   41            attach_console/0,           % Create a new console for thread.
   42            attach_console/1,           % ?Title
   43
   44            tspy/1,                     % :Spec
   45            tspy/2,                     % :Spec, +ThreadId
   46            tdebug/0,
   47            tdebug/1,                   % +ThreadId
   48            tnodebug/0,
   49            tnodebug/1,                 % +ThreadId
   50            tprofile/1,                 % +ThreadId
   51            tbacktrace/1,               % +ThreadId,
   52            tbacktrace/2                % +ThreadId, +Options
   53          ]).   54:- if(current_predicate(win_open_console/5)).   55:- export(( thread_run_interactor/0,    % interactor main loop
   56            interactor/0,
   57            interactor/1                % ?Title
   58          )).   59:- endif.   60
   61:- meta_predicate
   62    with_stopped_threads(0, +).   63
   64:- autoload(library(apply),[maplist/3]).   65:- autoload(library(backcomp),[thread_at_exit/1]).   66:- autoload(library(edinburgh),[nodebug/0]).   67:- autoload(library(lists),[max_list/2,append/2]).   68:- autoload(library(option),[merge_options/3,option/3]).   69:- autoload(library(prolog_stack),
   70	    [print_prolog_backtrace/2,get_prolog_backtrace/3]).   71:- autoload(library(statistics),[thread_statistics/2]).   72:- autoload(library(prolog_profile), [show_profile/1]).   73:- autoload(library(thread),[call_in_thread/2]).   74
   75:- set_prolog_flag(generate_debug_info, false).   76
   77:- module_transparent
   78    tspy/1,
   79    tspy/2.

Interactive thread utilities

This library provides utilities that are primarily intended for interactive usage in a threaded Prolog environment. It allows for inspecting threads, manage I/O of background threads (depending on the environment) and manipulating the debug status of threads. */

 threads
List currently known threads with their status.
   93threads :-
   94    threads(Threads),
   95    print_message(information, threads(Threads)).
   96
   97threads(Threads) :-
   98    findall(Thread, thread_statistics(_,Thread), Threads).
 join_threads
Join all terminated threads.
  104join_threads :-
  105    findall(Ripped, rip_thread(Ripped), AllRipped),
  106    (   AllRipped == []
  107    ->  true
  108    ;   print_message(informational, joined_threads(AllRipped))
  109    ).
  110
  111rip_thread(thread{id:id, status:Status}) :-
  112    thread_property(Id, status(Status)),
  113    Status \== running,
  114    \+ thread_self(Id),
  115    thread_join(Id, _).
 with_stopped_threads(:Goal, Options) is det
Stop all threads except the caller while running once(Goal). Note that this is in the thread user utilities as this is not something that should be used by normal applications. Notably, this may deadlock if the current thread requires input from some other thread to complete Goal or one of the stopped threads has a lock. Options:
stop_nodebug_threads(+Boolean)
If true (default false), also stop threads created with the debug(false) option.
except(+List)
Do not stop threads from this list.
bug
- Note that the threads are stopped when they process signals. As signal handling may be delayed, this implies they need not be stopped before Goal starts.
  136:- dynamic stopped_except/1.  137
  138with_stopped_threads(_, _) :-
  139    stopped_except(_),
  140    !.
  141with_stopped_threads(Goal, Options) :-
  142    thread_self(Me),
  143    setup_call_cleanup(
  144        asserta(stopped_except(Me), Ref),
  145        ( stop_other_threads(Me, Options),
  146          once(Goal)
  147        ),
  148        erase(Ref)).
  149
  150stop_other_threads(Me, Options) :-
  151    findall(T, stop_thread(Me, T, Options), Stopped),
  152    broadcast(stopped_threads(Stopped)).
  153
  154stop_thread(Me, Thread, Options) :-
  155    option(except(Except), Options, []),
  156    (   option(stop_nodebug_threads(true), Options)
  157    ->  thread_property(Thread, status(running))
  158    ;   debug_target(Thread)
  159    ),
  160    Me \== Thread,
  161    \+ memberchk(Thread, Except),
  162    catch(thread_signal(Thread, stopped_except), error(_,_), fail).
  163
  164stopped_except :-
  165    thread_wait(\+ stopped_except(_),
  166                [ wait_preds([stopped_except/1])
  167                ]).
 thread_has_console is semidet
True when the calling thread has an attached console.
See also
- attach_console/0
  175:- dynamic
  176    has_console/4.                  % Id, In, Out, Err
  177
  178thread_has_console(main) :- !.                  % we assume main has one.
  179thread_has_console(Id) :-
  180    has_console(Id, _, _, _).
  181
  182thread_has_console :-
  183    current_prolog_flag(break_level, _),
  184    !.
  185thread_has_console :-
  186    thread_self(Id),
  187    thread_has_console(Id),
  188    !.
 open_console(+Title, -In, -Out, -Err) is det
Open a new console window and unify In, Out and Err with the input, output and error streams for the new console. This predicate is only available if win_open_console/5 (Windows or Qt swipl-win) is provided.
To be done
- Port this to Epilog.
  199:- multifile xterm_args/1.  200:- dynamic   xterm_args/1.  201
  202:- if(current_predicate(win_open_console/5)).  203
  204can_open_console.
  205
  206open_console(Title, In, Out, Err) :-
  207    thread_self(Id),
  208    regkey(Id, Key),
  209    win_open_console(Title, In, Out, Err,
  210                     [ registry_key(Key)
  211                     ]).
  212
  213regkey(Key, Key) :-
  214    atom(Key).
  215regkey(_, 'Anonymous').
  216
  217:- endif.
 attach_console is det
 attach_console(?Title) is det
Create a new console and make the standard Prolog streams point to it. If not provided, the title is built using the thread id. Does nothing if the current thread already has a console attached.
  226attach_console :-
  227    attach_console(_).
  228
  229attach_console(_) :-
  230    thread_has_console,
  231    !.
  232:- if(current_predicate(open_console/4)).  233attach_console(Title) :-
  234    can_open_console,
  235    !,
  236    thread_self(Id),
  237    (   var(Title)
  238    ->  console_title(Id, Title)
  239    ;   true
  240    ),
  241    open_console(Title, In, Out, Err),
  242    assert(has_console(Id, In, Out, Err)),
  243    set_stream(In,  alias(user_input)),
  244    set_stream(Out, alias(user_output)),
  245    set_stream(Err, alias(user_error)),
  246    set_stream(In,  alias(current_input)),
  247    set_stream(Out, alias(current_output)),
  248    thread_at_exit(detach_console(Id)).
  249:- endif.  250attach_console(Title) :-
  251    print_message(error, cannot_attach_console(Title)),
  252    fail.
  253
  254:- if(current_predicate(open_console/4)).  255console_title(Thread, Title) :-         % uses tabbed consoles
  256    current_prolog_flag(console_menu_version, qt),
  257    !,
  258    human_thread_id(Thread, Id),
  259    format(atom(Title), 'Thread ~w', [Id]).
  260console_title(Thread, Title) :-
  261    current_prolog_flag(system_thread_id, SysId),
  262    human_thread_id(Thread, Id),
  263    format(atom(Title),
  264           'SWI-Prolog Thread ~w (~d) Interactor',
  265           [Id, SysId]).
  266
  267human_thread_id(Thread, Alias) :-
  268    thread_property(Thread, alias(Alias)),
  269    !.
  270human_thread_id(Thread, Id) :-
  271    thread_property(Thread, id(Id)).
 detach_console(+ThreadId) is det
Destroy the console for ThreadId.
  277detach_console(Id) :-
  278    (   retract(has_console(Id, In, Out, Err))
  279    ->  disable_line_editing(In, Out, Err),
  280        close(In, [force(true)]),
  281        close(Out, [force(true)]),
  282        close(Err, [force(true)])
  283    ;   true
  284    ).
 interactor is det
 interactor(?Title) is det
Run a Prolog toplevel in another thread with a new console window. If Title is given, this will be used as the window title.
  292interactor :-
  293    interactor(_).
  294
  295interactor(Title) :-
  296    current_prolog_flag(epilog, true),
  297    !,
  298    (   nonvar(Title)
  299    ->  Options = [title(Title)]
  300    ;   Options = []
  301    ),
  302    autoload_call(epilog(Options)).
  303interactor(Title) :-
  304    can_open_console,
  305    !,
  306    thread_self(Me),
  307    thread_create(thread_run_interactor(Me, Title), _Id,
  308                  [ detached(true)
  309                  ]),
  310    thread_get_message(Msg),
  311    (   Msg = title(Title0)
  312    ->  Title = Title0
  313    ;   Msg = throw(Error)
  314    ->  throw(Error)
  315    ;   Msg = false
  316    ->  fail
  317    ).
  318interactor(Title) :-
  319    print_message(error, cannot_attach_console(Title)),
  320    fail.
  321
  322thread_run_interactor(Creator, Title) :-
  323    set_prolog_flag(query_debug_settings, debug(false, false)),
  324    Error = error(Formal,_),
  325    (   catch(attach_console(Title), Error, true)
  326    ->  (   var(Formal)
  327        ->  thread_send_message(Creator, title(Title)),
  328            print_message(banner, thread_welcome),
  329            prolog
  330        ;   thread_send_message(Creator, throw(Error))
  331        )
  332    ;   thread_send_message(Creator, false)
  333    ).
 thread_run_interactor
Attach a console and run a Prolog toplevel in the current thread.
  339thread_run_interactor :-
  340    set_prolog_flag(query_debug_settings, debug(false, false)),
  341    attach_console(_Title),
  342    print_message(banner, thread_welcome),
  343    prolog.
  344
  345:- endif.                               % have open_console/4
  346
  347                 /*******************************
  348                 *          DEBUGGING           *
  349                 *******************************/
 tspy(:Spec) is det
 tspy(:Spec, +ThreadId) is det
Trap the graphical debugger on reaching Spec in the specified or any thread.
  357tspy(Spec) :-
  358    spy(Spec),
  359    tdebug.
  360
  361tspy(Spec, ThreadID) :-
  362    spy(Spec),
  363    tdebug(ThreadID).
 tdebug is det
 tdebug(+Thread) is det
Enable debug-mode, trapping the graphical debugger on reaching spy-points or errors.
  372tdebug :-
  373    forall(debug_target(Id), thread_signal(Id, debug_thread)).
  374
  375tdebug(ThreadID) :-
  376    thread_signal(ThreadID, debug_thread).
  377
  378debug_thread :-
  379    current_prolog_flag(gui, true),
  380    !,
  381    autoload_call(gdebug).
  382debug_thread :-
  383    debug.
 tnodebug is det
 tnodebug(+Thread) is det
Disable debug-mode in all threads or the specified Thread.
  391tnodebug :-
  392    forall(debug_target(Id), thread_signal(Id, nodebug)).
  393
  394tnodebug(ThreadID) :-
  395    thread_signal(ThreadID, nodebug).
  396
  397
  398debug_target(Thread) :-
  399    thread_property(Thread, status(running)),
  400    thread_property(Thread, debug(true)).
 tbacktrace(+Thread) is det
 tbacktrace(+Thread, +Options) is det
Print a backtrace for Thread to the stream user_error of the calling thread. This is achieved by inserting an interrupt into Thread using call_in_thread/2. Options:
depth(+MaxFrames)
Number of stack frames to show. Default is the current Prolog flag backtrace_depth or 20.

Other options are passed to get_prolog_backtrace/3.

bug
- call_in_thread/2 may not process the event.
  417tbacktrace(Thread) :-
  418    tbacktrace(Thread, []).
  419
  420tbacktrace(Thread, Options) :-
  421    merge_options(Options, [clause_references(false)], Options1),
  422    (   current_prolog_flag(backtrace_depth, Default)
  423    ->  true
  424    ;   Default = 20
  425    ),
  426    option(depth(Depth), Options1, Default),
  427    call_in_thread(Thread, thread_get_prolog_backtrace(Depth, Stack, Options1)),
  428    print_prolog_backtrace(user_error, Stack).
 thread_get_prolog_backtrace(+Depth, -Stack, +Options)
As get_prolog_backtrace/3, but starts above the C callback, hiding the overhead inside call_in_thread/2.
  435thread_get_prolog_backtrace(Depth, Stack, Options) :-
  436    prolog_current_frame(Frame),
  437    signal_frame(Frame, SigFrame),
  438    get_prolog_backtrace(Depth, Stack, [frame(SigFrame)|Options]).
  439
  440signal_frame(Frame, SigFrame) :-
  441    prolog_frame_attribute(Frame, clause, _),
  442    !,
  443    (   prolog_frame_attribute(Frame, parent, Parent)
  444    ->  signal_frame(Parent, SigFrame)
  445    ;   SigFrame = Frame
  446    ).
  447signal_frame(Frame, SigFrame) :-
  448    (   prolog_frame_attribute(Frame, parent, Parent)
  449    ->  SigFrame = Parent
  450    ;   SigFrame = Frame
  451    ).
  452
  453
  454
  455                 /*******************************
  456                 *       REMOTE PROFILING       *
  457                 *******************************/
 tprofile(+Thread) is det
Profile the operation of Thread until the user hits a key.
  463tprofile(Thread) :-
  464    init_pce,
  465    thread_signal(Thread,
  466                  (   reset_profiler,
  467                      profiler(_, true)
  468                  )),
  469    format('Running profiler in thread ~w (press RET to show results) ...',
  470           [Thread]),
  471    flush_output,
  472    get_code(_),
  473    thread_signal(Thread,
  474                  (   profiler(_, false),
  475                      show_profile([])
  476                  )).
 init_pce
Make sure XPCE is running if it is attached, so we can use the graphical display using in_pce_thread/1.
  484:- if(exists_source(library(pce))).  485init_pce :-
  486    current_prolog_flag(gui, true),
  487    !,
  488    autoload_call(send(@(display), open)).
  489:- endif.  490init_pce.
  491
  492
  493                 /*******************************
  494                 *             HOOKS            *
  495                 *******************************/
  496
  497:- multifile
  498    user:message_hook/3.  499
  500user:message_hook(trace_mode(on), _, Lines) :-
  501    \+ thread_has_console,
  502    \+ current_prolog_flag(gui_tracer, true),
  503    catch(attach_console, _, fail),
  504    print_message_lines(user_error, '% ', Lines).
  505
  506:- multifile
  507    prolog:message/3.  508
  509prolog:message(thread_welcome) -->
  510    { thread_self(Self),
  511      human_thread_id(Self, Id)
  512    },
  513    [ 'SWI-Prolog console for thread ~w'-[Id],
  514      nl, nl
  515    ].
  516prolog:message(joined_threads(Threads)) -->
  517    [ 'Joined the following threads'-[], nl ],
  518    thread_list(Threads).
  519prolog:message(threads(Threads)) -->
  520    thread_list(Threads).
  521prolog:message(cannot_attach_console(_Title)) -->
  522    [ 'Cannot attach a console (requires swipl-win or POSIX pty support)' ].
  523
  524thread_list(Threads) -->
  525    { maplist(th_id_len, Threads, Lens),
  526      max_list(Lens, MaxWidth),
  527      LeftColWidth is max(6, MaxWidth),
  528      Threads = [H|_]
  529    },
  530    thread_list_header(H, LeftColWidth),
  531    thread_list(Threads, LeftColWidth).
  532
  533th_id_len(Thread, IdLen) :-
  534    write_length(Thread.id, IdLen, [quoted(true)]).
  535
  536thread_list([], _) --> [].
  537thread_list([H|T], CW) -->
  538    thread_info(H, CW),
  539    (   {T == []}
  540    ->  []
  541    ;   [nl],
  542        thread_list(T, CW)
  543    ).
  544
  545thread_list_header(Thread, CW) -->
  546    { _{id:_, status:_, time:_, stacks:_} :< Thread,
  547      !,
  548      HrWidth is CW+18+13+13
  549    },
  550    [ '~|~tThread~*+ Status~tTime~18+~tStack use~13+~tallocated~13+'-[CW], nl ],
  551    [ '~|~`-t~*+'-[HrWidth], nl ].
  552thread_list_header(Thread, CW) -->
  553    { _{id:_, status:_} :< Thread,
  554      !,
  555      HrWidth is CW+7
  556    },
  557    [ '~|~tThread~*+ Status'-[CW], nl ],
  558    [ '~|~`-t~*+'-[HrWidth], nl ].
  559
  560thread_info(Thread, CW) -->
  561    { _{id:Id, status:Status, time:Time, stacks:Stacks} :< Thread },
  562    !,
  563    [ '~|~t~q~*+ ~w~t~3f~18+~t~D~13+~t~D~13+'-
  564      [ Id, CW, Status, Time.cpu, Stacks.total.usage, Stacks.total.allocated
  565      ]
  566    ].
  567thread_info(Thread, CW) -->
  568    { _{id:Id, status:Status} :< Thread },
  569    !,
  570    [ '~|~t~q~*+ ~w'-
  571      [ Id, CW, Status
  572      ]
  573    ]