2004-01-23 02:23:51 +00:00
|
|
|
/*************************************************************************
|
2008-04-04 00:23:06 +01:00
|
|
|
* *
|
|
|
|
* YAP Prolog *
|
|
|
|
* *
|
|
|
|
* Yap Prolog was developed at NCCUP - Universidade do Porto *
|
|
|
|
* *
|
|
|
|
* Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997 *
|
|
|
|
* *
|
2004-01-23 02:23:51 +00:00
|
|
|
**************************************************************************
|
2008-04-04 00:23:06 +01:00
|
|
|
* *
|
|
|
|
* File: threads.yap *
|
|
|
|
* Last rev: 8/2/88 *
|
|
|
|
* mods: *
|
|
|
|
* comments: support threads *
|
|
|
|
* *
|
2004-01-23 02:23:51 +00:00
|
|
|
*************************************************************************/
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/**
|
2014-09-15 09:13:50 +01:00
|
|
|
@addtogroup Threads
|
|
|
|
@defgroup Threads Threads
|
|
|
|
@ingroup YAPExtensions
|
2014-09-11 20:06:57 +01:00
|
|
|
@{
|
2014-09-15 09:13:50 +01:00
|
|
|
|
|
|
|
@page YAPThreads Threads in YAP
|
|
|
|
|
|
|
|
YAP implements a SWI-Prolog compatible multithreading
|
|
|
|
library. Like in SWI-Prolog, Prolog threads have their own stacks and
|
|
|
|
only share the Prolog <em>heap</em>: predicates, records, flags and other
|
|
|
|
global non-backtrackable data. The package is based on the POSIX thread
|
|
|
|
standard (Butenhof:1997:PPT) used on most popular systems except
|
|
|
|
for MS-Windows.
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
*/
|
|
|
|
|
2014-04-09 12:39:29 +01:00
|
|
|
:- system_module( '$_threads', [current_mutex/3,
|
|
|
|
current_thread/2,
|
|
|
|
message_queue_create/1,
|
|
|
|
message_queue_create/2,
|
|
|
|
message_queue_destroy/1,
|
|
|
|
message_queue_property/2,
|
|
|
|
mutex_create/1,
|
|
|
|
mutex_create/2,
|
|
|
|
mutex_destroy/1,
|
|
|
|
mutex_lock/1,
|
|
|
|
mutex_property/2,
|
|
|
|
mutex_trylock/1,
|
|
|
|
mutex_unlock/1,
|
|
|
|
mutex_unlock_all/0,
|
|
|
|
thread_at_exit/1,
|
|
|
|
thread_cancel/1,
|
|
|
|
thread_create/1,
|
|
|
|
thread_create/2,
|
|
|
|
thread_create/3,
|
|
|
|
thread_default/1,
|
|
|
|
thread_defaults/1,
|
|
|
|
thread_detach/1,
|
|
|
|
thread_exit/1,
|
|
|
|
thread_get_message/1,
|
|
|
|
thread_get_message/2,
|
|
|
|
thread_join/2,
|
|
|
|
(thread_local)/1,
|
|
|
|
thread_peek_message/1,
|
|
|
|
thread_peek_message/2,
|
|
|
|
thread_property/1,
|
|
|
|
thread_property/2,
|
|
|
|
thread_self/1,
|
|
|
|
thread_send_message/1,
|
|
|
|
thread_send_message/2,
|
|
|
|
thread_set_default/1,
|
|
|
|
thread_set_defaults/1,
|
|
|
|
thread_signal/2,
|
|
|
|
thread_sleep/1,
|
|
|
|
threads/0,
|
|
|
|
(volatile)/1,
|
|
|
|
with_mutex/2], ['$reinit_thread0'/0,
|
|
|
|
'$thread_gfetch'/1,
|
|
|
|
'$thread_local'/2]).
|
|
|
|
|
|
|
|
:- use_system_module( '$_boot', ['$check_callable'/2,
|
|
|
|
'$run_at_thread_start'/0,
|
|
|
|
'$system_catch'/4]).
|
|
|
|
|
|
|
|
:- use_system_module( '$_errors', ['$do_error'/2]).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2004-02-05 16:57:02 +00:00
|
|
|
:- meta_predicate
|
2010-06-14 08:56:21 +01:00
|
|
|
thread_initialization(0),
|
|
|
|
thread_at_exit(0),
|
|
|
|
thread_create(0, -, :),
|
|
|
|
thread_create(0, -),
|
|
|
|
thread_create(0),
|
|
|
|
thread_signal(+, 0),
|
|
|
|
with_mutex(+, 0),
|
|
|
|
thread_signal(+,0),
|
2009-12-04 18:24:22 +00:00
|
|
|
volatile(:).
|
|
|
|
|
|
|
|
volatile(P) :- var(P),
|
|
|
|
throw(error(instantiation_error,volatile(P))).
|
|
|
|
volatile(M:P) :-
|
|
|
|
'$do_volatile'(P,M).
|
|
|
|
volatile((G1,G2)) :-
|
2011-07-10 12:52:33 +01:00
|
|
|
'$current_module'(M),
|
|
|
|
'$do_volatile'(G1,M),
|
|
|
|
'$do_volatile'(G2,M).
|
2009-12-04 18:24:22 +00:00
|
|
|
volatile(P) :-
|
|
|
|
'$current_module'(M),
|
|
|
|
'$do_volatile'(P,M).
|
|
|
|
|
|
|
|
'$do_volatile'(P,M) :- dynamic(M:P).
|
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @defgroup Creating_and_Destroying_Prolog_Threads Creating and Destroying Prolog Threads
|
|
|
|
@ingroup Threads
|
|
|
|
|
|
|
|
@{
|
|
|
|
*/
|
|
|
|
|
2004-02-11 01:20:56 +00:00
|
|
|
:- initialization('$init_thread0').
|
|
|
|
|
|
|
|
'$init_thread0' :-
|
2009-12-18 01:55:09 +00:00
|
|
|
recorda('$thread_alias', [0|main], _),
|
|
|
|
fail.
|
|
|
|
'$init_thread0' :-
|
|
|
|
'$no_threads', !.
|
2014-10-13 12:34:52 +01:00
|
|
|
'$init_thread0' :-
|
|
|
|
recorda('$thread_defaults', [0, 0, 0, false, true], _).
|
2004-02-11 01:20:56 +00:00
|
|
|
|
2012-08-22 21:29:28 +01:00
|
|
|
'$reinit_thread0' :-
|
|
|
|
'$no_threads', !.
|
2014-10-14 15:53:24 +01:00
|
|
|
'$reinit_thread0'.
|
2014-10-13 12:34:52 +01:00
|
|
|
|
2012-06-26 13:02:44 +01:00
|
|
|
|
2004-02-11 01:20:56 +00:00
|
|
|
'$top_thread_goal'(G, Detached) :-
|
|
|
|
'$thread_self'(Id),
|
|
|
|
(Detached == true -> '$detach_thread'(Id) ; true),
|
2004-01-23 02:23:51 +00:00
|
|
|
'$current_module'(Module),
|
2009-04-25 18:54:21 +01:00
|
|
|
'$run_at_thread_start',
|
2008-01-23 17:57:56 +00:00
|
|
|
% always finish with a throw to make sure we clean stacks.
|
2008-08-07 21:51:23 +01:00
|
|
|
'$system_catch'((G -> throw('$thread_finished'(true)) ; throw('$thread_finished'(false))),Module,Exception,'$close_thread'(Exception,Detached)),
|
|
|
|
% force backtracking and handling exceptions
|
|
|
|
fail.
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2011-03-11 19:49:32 +00:00
|
|
|
'$close_thread'(Status, _Detached) :-
|
2008-01-27 11:01:07 +00:00
|
|
|
'$thread_zombie_self'(Id0), !,
|
2008-08-06 18:32:22 +01:00
|
|
|
'$record_thread_status'(Id0,Status),
|
2008-01-27 11:01:07 +00:00
|
|
|
'$run_at_thread_exit'(Id0),
|
2008-08-06 18:32:22 +01:00
|
|
|
'$erase_thread_info'(Id0).
|
|
|
|
|
|
|
|
% OK, we want to ensure atomicity here in case we get an exception while we
|
|
|
|
% are closing down the thread.
|
|
|
|
'$record_thread_status'(Id0,Stat) :- !,
|
2008-08-07 21:51:23 +01:00
|
|
|
'$mk_tstatus_key'(Id0, Key),
|
|
|
|
(recorded(Key, _, R), erase(R), fail
|
2008-08-06 18:32:22 +01:00
|
|
|
;
|
|
|
|
Stat = '$thread_finished'(Status) ->
|
2008-08-07 21:51:23 +01:00
|
|
|
recorda(Key, Status, _)
|
2008-08-06 18:32:22 +01:00
|
|
|
;
|
2008-08-07 21:51:23 +01:00
|
|
|
recorda(Key, exception(Stat), _)
|
2008-08-06 18:32:22 +01:00
|
|
|
).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @pred thread_create(: _Goal_)
|
|
|
|
|
|
|
|
|
|
|
|
Create a new Prolog detached thread using default options. See thread_create/3.
|
|
|
|
|
|
|
|
*/
|
2007-06-19 11:55:35 +01:00
|
|
|
thread_create(Goal) :-
|
|
|
|
G0 = thread_create(Goal),
|
|
|
|
'$check_callable'(Goal, G0),
|
2008-03-28 10:57:28 +00:00
|
|
|
'$thread_options'([detached(true)], [], Stack, Trail, System, Detached, AtExit, G0),
|
2007-06-19 11:55:35 +01:00
|
|
|
'$thread_new_tid'(Id),
|
2008-08-06 18:32:22 +01:00
|
|
|
% '$erase_thread_info'(Id), % this should not be here
|
2008-04-02 18:37:07 +01:00
|
|
|
(
|
2008-08-07 21:51:23 +01:00
|
|
|
'$create_thread'(Goal, Stack, Trail, System, Detached, AtExit, Id)
|
2008-04-02 18:37:07 +01:00
|
|
|
->
|
|
|
|
true
|
|
|
|
;
|
2008-08-07 21:51:23 +01:00
|
|
|
'$mk_tstatus_key'(Id, Key),
|
|
|
|
recorda(Key, exception(resource_error(memory)),_)
|
2008-04-02 18:37:07 +01:00
|
|
|
).
|
2007-06-19 11:55:35 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @pred thread_create(: _Goal_, - _Id_)
|
|
|
|
|
|
|
|
|
|
|
|
Create a new Prolog thread using default options. See thread_create/3.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2008-04-03 01:10:04 +01:00
|
|
|
thread_create(Goal, Id) :-
|
2006-12-31 19:33:27 +00:00
|
|
|
G0 = thread_create(Goal, Id),
|
2007-01-01 19:40:17 +00:00
|
|
|
'$check_callable'(Goal, G0),
|
2010-10-12 22:02:24 +01:00
|
|
|
( nonvar(Id) -> '$do_error'(uninstantiation_error(Id),G0) ; true ),
|
2008-03-28 10:57:28 +00:00
|
|
|
'$thread_options'([], [], Stack, Trail, System, Detached, AtExit, G0),
|
2006-05-24 22:41:00 +01:00
|
|
|
'$thread_new_tid'(Id),
|
2008-08-06 18:32:22 +01:00
|
|
|
% '$erase_thread_info'(Id), % this should not be here
|
2008-04-02 18:37:07 +01:00
|
|
|
(
|
2008-08-07 21:51:23 +01:00
|
|
|
'$create_thread'(Goal, Stack, Trail, System, Detached, AtExit, Id)
|
2008-04-02 18:37:07 +01:00
|
|
|
->
|
|
|
|
true
|
|
|
|
;
|
2008-08-07 21:51:23 +01:00
|
|
|
'$mk_tstatus_key'(Id, Key),
|
|
|
|
recorda(Key, exception(resource_error(memory)),_)
|
2008-04-03 01:10:04 +01:00
|
|
|
).
|
2006-05-24 22:41:00 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
@pred thread_create(: _Goal_, - _Id_, + _Options_)
|
|
|
|
|
|
|
|
Create a new Prolog thread (and underlying C-thread) and start it
|
|
|
|
by executing _Goal_. If the thread is created successfully, the
|
|
|
|
thread-identifier of the created thread is unified to _Id_.
|
|
|
|
_Options_ is a list of options. Currently defined options are:
|
|
|
|
|
|
|
|
+ stack
|
|
|
|
Set the limit in K-Bytes to which the Prolog stacks of
|
|
|
|
this thread may grow. If omitted, the limit of the calling thread is
|
|
|
|
used. See also the commandline `-S` option.
|
|
|
|
|
|
|
|
+ trail
|
|
|
|
Set the limit in K-Bytes to which the trail stack of this thread may
|
|
|
|
grow. If omitted, the limit of the calling thread is used. See also the
|
|
|
|
commandline option `-T`.
|
|
|
|
|
|
|
|
+ alias
|
|
|
|
Associate an alias-name with the thread. This named may be used to
|
|
|
|
refer to the thread and remains valid until the thread is joined
|
|
|
|
(see thread_join/2).
|
|
|
|
|
|
|
|
+ at_exit
|
|
|
|
Define an exit hook for the thread. This hook is called when the thread
|
|
|
|
terminates, no matter its exit status.
|
|
|
|
|
|
|
|
+ detached
|
|
|
|
If `false` (default), the thread can be waited for using
|
|
|
|
thread_join/2. thread_join/2 must be called on this thread
|
|
|
|
to reclaim the all resources associated to the thread. If `true`,
|
|
|
|
the system will reclaim all associated resources automatically after the
|
|
|
|
thread finishes. Please note that thread identifiers are freed for reuse
|
|
|
|
after a detached thread finishes or a normal thread has been joined.
|
|
|
|
See also thread_join/2 and thread_detach/1.
|
|
|
|
|
|
|
|
|
|
|
|
The _Goal_ argument is <em>copied</em> to the new Prolog engine.
|
|
|
|
This implies further instantiation of this term in either thread does
|
|
|
|
not have consequences for the other thread: Prolog threads do not share
|
|
|
|
data from their stacks.
|
|
|
|
*/
|
2008-04-03 01:10:04 +01:00
|
|
|
thread_create(Goal, Id, Options) :-
|
2004-01-23 02:23:51 +00:00
|
|
|
G0 = thread_create(Goal, Id, Options),
|
|
|
|
'$check_callable'(Goal,G0),
|
2010-10-12 22:02:24 +01:00
|
|
|
( nonvar(Id) -> '$do_error'(uninstantiation_error(Id),G0) ; true ),
|
2008-03-28 10:57:28 +00:00
|
|
|
'$thread_options'(Options, Alias, Stack, Trail, System, Detached, AtExit, G0),
|
2004-02-11 01:20:56 +00:00
|
|
|
'$thread_new_tid'(Id),
|
2008-08-06 18:32:22 +01:00
|
|
|
% '$erase_thread_info'(Id), % this should not be here
|
2008-08-07 21:51:23 +01:00
|
|
|
'$record_alias_info'(Id, Alias),
|
2008-04-02 18:37:07 +01:00
|
|
|
(
|
2008-08-07 21:51:23 +01:00
|
|
|
'$create_thread'(Goal, Stack, Trail, System, Detached, AtExit, Id)
|
2008-04-02 18:37:07 +01:00
|
|
|
->
|
|
|
|
true
|
|
|
|
;
|
2008-08-07 21:51:23 +01:00
|
|
|
'$mk_tstatus_key'(Id, Key),
|
|
|
|
recorda(Key, exception(resource_error(memory)),_)
|
2008-04-03 01:10:04 +01:00
|
|
|
).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2007-01-02 02:51:31 +00:00
|
|
|
'$erase_thread_info'(Id) :-
|
2004-01-23 02:23:51 +00:00
|
|
|
recorded('$thread_alias',[Id|_],R),
|
|
|
|
erase(R),
|
|
|
|
fail.
|
2007-01-02 02:51:31 +00:00
|
|
|
'$erase_thread_info'(Id) :-
|
|
|
|
recorded('$thread_exit_hook', [Id|_], R),
|
|
|
|
erase(R),
|
|
|
|
fail.
|
|
|
|
'$erase_thread_info'(_).
|
2006-05-24 15:40:50 +01:00
|
|
|
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_options'(Opts, Alias, Stack, Trail, System, Detached, AtExit, G) :-
|
|
|
|
strip_module(Opts, Mod, LOpts),
|
|
|
|
(
|
|
|
|
var(Opts)
|
|
|
|
->
|
|
|
|
'$do_error'(instantiation_error,G)
|
|
|
|
;
|
|
|
|
var(Mod)
|
|
|
|
->
|
|
|
|
'$do_error'(instantiation_error,G)
|
|
|
|
;
|
|
|
|
\+ atom(Mod)
|
|
|
|
->
|
2010-10-12 22:02:24 +01:00
|
|
|
'$do_error'(uninstantiation_error(Mod),G)
|
2009-05-30 16:43:03 +01:00
|
|
|
;
|
|
|
|
var(LOpts)
|
|
|
|
->
|
|
|
|
'$do_error'(instantiation_error,G)
|
|
|
|
;
|
|
|
|
'$thread_options'(LOpts, Alias, Stack, Trail, System, Detached, AtExit, Mod, G)
|
|
|
|
).
|
|
|
|
|
2014-10-07 01:35:41 +01:00
|
|
|
'$thread_options'([], _, Stack, Trail, System, Detached, AtExit, _M, _) :-
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults', [DefaultStack, DefaultTrail, DefaultSystem, DefaultDetached, DefaultAtExit], _),
|
2006-05-24 16:27:45 +01:00
|
|
|
( var(Stack) -> Stack = DefaultStack; true ),
|
|
|
|
( var(Trail) -> Trail = DefaultTrail; true ),
|
2006-12-31 19:33:27 +00:00
|
|
|
( var(System) -> System = DefaultSystem; true ),
|
2008-03-28 10:57:28 +00:00
|
|
|
( var(Detached) -> Detached = DefaultDetached; true ),
|
|
|
|
( var(AtExit) -> AtExit = DefaultAtExit; true ).
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_options'([Opt|Opts], Alias, Stack, Trail, System, Detached, AtExit, M, G0) :-
|
|
|
|
'$thread_option'(Opt, Alias, Stack, Trail, System, Detached, AtExit, M, G0),
|
|
|
|
'$thread_options'(Opts, Alias, Stack, Trail, System, Detached, AtExit, M, G0).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_option'(Option, _, _, _, _, _, _, _, G0) :- var(Option), !,
|
2004-02-11 01:20:56 +00:00
|
|
|
'$do_error'(instantiation_error,G0).
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_option'(alias(Alias), Alias, _, _, _, _, _, _, G0) :- !,
|
2008-03-31 17:26:31 +01:00
|
|
|
( \+ atom(Alias) -> '$do_error'(type_error(atom,Alias),G0) ; true ).
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_option'(stack(Stack), _, Stack, _, _, _, _, _, G0) :- !,
|
2004-01-23 02:23:51 +00:00
|
|
|
( \+ integer(Stack) -> '$do_error'(type_error(integer,Stack),G0) ; true ).
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_option'(trail(Trail), _, _, Trail, _, _, _, _, G0) :- !,
|
2004-01-23 02:23:51 +00:00
|
|
|
( \+ integer(Trail) -> '$do_error'(type_error(integer,Trail),G0) ; true ).
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_option'(system(System), _, _, _, System, _, _, _, G0) :- !,
|
2004-01-23 02:23:51 +00:00
|
|
|
( \+ integer(System) -> '$do_error'(type_error(integer,System),G0) ; true ).
|
2009-05-30 16:43:03 +01:00
|
|
|
'$thread_option'(detached(Detached), _, _, _, _, Detached, _, _, G0) :- !,
|
2008-03-31 17:26:31 +01:00
|
|
|
( Detached \== true, Detached \== false -> '$do_error'(domain_error(thread_option,Detached+[true,false]),G0) ; true ).
|
2014-10-07 01:35:41 +01:00
|
|
|
'$thread_option'(at_exit(AtExit), _, _, _, _, _, AtExit, _M, G0) :- !,
|
2008-03-31 17:26:31 +01:00
|
|
|
( \+ callable(AtExit) -> '$do_error'(type_error(callable,AtExit),G0) ; true ).
|
2011-03-11 20:36:00 +00:00
|
|
|
% succeed silently, like SWI.
|
2014-10-07 01:35:41 +01:00
|
|
|
'$thread_option'(_Option, _, _, _, _, _, _, _, _G0).
|
2011-03-11 20:36:00 +00:00
|
|
|
% '$do_error'(domain_error(thread_option,Option),G0).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2008-08-07 21:51:23 +01:00
|
|
|
'$record_alias_info'(_, Alias) :-
|
|
|
|
var(Alias), !.
|
|
|
|
'$record_alias_info'(_, Alias) :-
|
2007-01-02 02:51:31 +00:00
|
|
|
recorded('$thread_alias', [_|Alias], _), !,
|
2014-10-07 01:35:41 +01:00
|
|
|
'$do_error'(permission_error(create,thread,alias(Alias)), create_thread).
|
2008-08-07 21:51:23 +01:00
|
|
|
'$record_alias_info'(Id, Alias) :-
|
|
|
|
recorda('$thread_alias', [Id|Alias], _).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2007-01-24 14:20:04 +00:00
|
|
|
% vsc: ?????
|
2007-01-02 02:51:31 +00:00
|
|
|
thread_defaults(Defaults) :-
|
|
|
|
nonvar(Defaults), !,
|
2012-03-22 22:13:11 +00:00
|
|
|
'$do_error'(uninstantiation_error(Defaults), thread_defaults(Defaults)).
|
2008-03-28 10:57:28 +00:00
|
|
|
thread_defaults([stack(Stack), trail(Trail), system(System), detached(Detached), at_exit(AtExit)]) :-
|
|
|
|
recorded('$thread_defaults',[Stack, Trail, System, Detached, AtExit], _).
|
2006-05-24 15:40:50 +01:00
|
|
|
|
2006-12-31 20:33:55 +00:00
|
|
|
thread_default(Default) :-
|
|
|
|
var(Default), !,
|
|
|
|
recorded('$thread_defaults', Defaults, _),
|
|
|
|
'$thread_default'(Default, Defaults).
|
|
|
|
thread_default(stack(Stack)) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults',[Stack, _, _, _, _], _).
|
2006-12-31 20:33:55 +00:00
|
|
|
thread_default(trail(Trail)) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults',[_, Trail, _, _, _], _).
|
2006-12-31 20:33:55 +00:00
|
|
|
thread_default(system(System)) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults',[_, _, System, _, _], _).
|
2006-12-31 20:33:55 +00:00
|
|
|
thread_default(detached(Detached)) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults',[_, _, _, Detached, _], _).
|
|
|
|
thread_default(at_exit(AtExit)) :- !,
|
|
|
|
recorded('$thread_defaults',[_, _, _, _, AtExit], _).
|
2006-12-31 20:33:55 +00:00
|
|
|
thread_default(Default) :-
|
|
|
|
'$do_error'(type_error(thread_option,Default),thread_default(Default)).
|
|
|
|
|
2008-03-28 10:57:28 +00:00
|
|
|
'$thread_default'(stack(Stack), [Stack, _, _, _, _]).
|
|
|
|
'$thread_default'(trail(Trail), [_, Trail, _, _, _]).
|
|
|
|
'$thread_default'(stack(System), [_, _, System, _, _]).
|
|
|
|
'$thread_default'(detached(Detached), [_, _, _, Detached, _]).
|
|
|
|
'$thread_default'(at_exit(AtExit), [_, _, _, _, AtExit]).
|
2006-12-31 20:33:55 +00:00
|
|
|
|
2006-05-24 15:40:50 +01:00
|
|
|
thread_set_defaults(V) :- var(V), !,
|
|
|
|
'$do_error'(instantiation_error, thread_set_defaults(V)).
|
|
|
|
thread_set_defaults([Default| Defaults]) :- !,
|
|
|
|
'$thread_set_defaults'([Default| Defaults], thread_set_defaults([Default| Defaults])).
|
|
|
|
thread_set_defaults(T) :-
|
|
|
|
'$do_error'(type_error(list, T), thread_set_defaults(T)).
|
|
|
|
|
|
|
|
'$thread_set_defaults'([], _).
|
|
|
|
'$thread_set_defaults'([Default| Defaults], G) :- !,
|
|
|
|
'$thread_set_default'(Default, G),
|
|
|
|
'$thread_set_defaults'(Defaults, G).
|
|
|
|
|
2006-12-31 20:33:55 +00:00
|
|
|
thread_set_default(V) :- var(V), !,
|
|
|
|
'$do_error'(instantiation_error, thread_set_default(V)).
|
|
|
|
thread_set_default(Default) :-
|
|
|
|
'$thread_set_default'(Default, thread_set_default(Default)).
|
|
|
|
|
2006-05-24 15:40:50 +01:00
|
|
|
'$thread_set_default'(stack(Stack), G) :-
|
|
|
|
\+ integer(Stack), !,
|
|
|
|
'$do_error'(type_error(integer, Stack), G).
|
|
|
|
'$thread_set_default'(stack(Stack), G) :-
|
|
|
|
Stack < 0, !,
|
|
|
|
'$do_error'(domain_error(not_less_than_zero, Stack), G).
|
2007-01-24 14:20:04 +00:00
|
|
|
'$thread_set_default'(stack(Stack), _) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults', [_, Trail, System, Detached, AtExit], Ref),
|
2006-12-31 20:33:55 +00:00
|
|
|
erase(Ref),
|
2008-03-28 10:57:28 +00:00
|
|
|
recorda('$thread_defaults', [Stack, Trail, System, Detached, AtExit], _).
|
2006-05-24 15:40:50 +01:00
|
|
|
|
|
|
|
'$thread_set_default'(trail(Trail), G) :-
|
|
|
|
\+ integer(Trail), !,
|
|
|
|
'$do_error'(type_error(integer, Trail), G).
|
|
|
|
'$thread_set_default'(trail(Trail), G) :-
|
|
|
|
Trail < 0, !,
|
|
|
|
'$do_error'(domain_error(not_less_than_zero, Trail), G).
|
2007-01-24 14:20:04 +00:00
|
|
|
'$thread_set_default'(trail(Trail), _) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults', [Stack, _, System, Detached, AtExit], Ref),
|
2006-12-31 20:33:55 +00:00
|
|
|
erase(Ref),
|
2008-03-28 10:57:28 +00:00
|
|
|
recorda('$thread_defaults', [Stack, Trail, System, Detached, AtExit], _).
|
2006-05-24 15:40:50 +01:00
|
|
|
|
|
|
|
'$thread_set_default'(system(System), G) :-
|
|
|
|
\+ integer(System), !,
|
|
|
|
'$do_error'(type_error(integer, System), G).
|
|
|
|
'$thread_set_default'(system(System), G0) :-
|
|
|
|
System < 0, !,
|
|
|
|
'$do_error'(domain_error(not_less_than_zero, System), G0).
|
2007-01-24 14:20:04 +00:00
|
|
|
'$thread_set_default'(system(System), _) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults', [Stack, Trail, _, Detached, AtExit], Ref),
|
2006-12-31 20:33:55 +00:00
|
|
|
erase(Ref),
|
2008-03-28 10:57:28 +00:00
|
|
|
recorda('$thread_defaults', [Stack, Trail, System, Detached, AtExit], _).
|
2006-12-31 19:33:27 +00:00
|
|
|
|
|
|
|
'$thread_set_default'(detached(Detached), G) :-
|
|
|
|
Detached \== true, Detached \== false, !,
|
|
|
|
'$do_error'(type_error(boolean, Detached), G).
|
2007-01-24 14:20:04 +00:00
|
|
|
'$thread_set_default'(detached(Detached), _) :- !,
|
2008-03-28 10:57:28 +00:00
|
|
|
recorded('$thread_defaults', [Stack, Trail, System, _, AtExit], Ref),
|
2006-12-31 20:33:55 +00:00
|
|
|
erase(Ref),
|
2008-03-28 10:57:28 +00:00
|
|
|
recorda('$thread_defaults', [Stack, Trail, System, Detached, AtExit], _).
|
|
|
|
|
|
|
|
'$thread_set_default'(at_exit(AtExit), G) :-
|
|
|
|
\+ callable(AtExit), !,
|
|
|
|
'$do_error'(type_error(callable, AtExit), G).
|
|
|
|
'$thread_set_default'(at_exit(AtExit), _) :- !,
|
|
|
|
recorded('$thread_defaults', [Stack, Trail, System, Detached, _], Ref),
|
|
|
|
erase(Ref),
|
2008-04-04 10:32:10 +01:00
|
|
|
'$current_module'(M),
|
|
|
|
recorda('$thread_defaults', [Stack, Trail, System, Detached, M:AtExit], _).
|
2006-05-24 15:40:50 +01:00
|
|
|
|
|
|
|
'$thread_set_default'(Default, G) :-
|
|
|
|
'$do_error'(domain_error(thread_default, Default), G).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_self(- _Id_)
|
|
|
|
|
|
|
|
|
|
|
|
Get the Prolog thread identifier of the running thread. If the thread
|
|
|
|
has an alias, the alias-name is returned.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2007-01-01 16:54:39 +00:00
|
|
|
thread_self(Id) :-
|
|
|
|
nonvar(Id), \+ integer(Id), \+ atom(Id), !,
|
|
|
|
'$do_error'(domain_error(thread_or_alias, Id), thread_self(Id)).
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_self(Id) :-
|
|
|
|
'$thread_self'(Id0),
|
2007-01-02 03:01:20 +00:00
|
|
|
'$thread_id_alias'(Id0, Id).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2007-01-01 16:54:39 +00:00
|
|
|
/* Exit status may be either true, false, exception(Term), or exited(Term) */
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_join(+ _Id_, - _Status_)
|
|
|
|
|
|
|
|
|
|
|
|
Wait for the termination of thread with given _Id_. Then unify the
|
|
|
|
result-status of the thread with _Status_. After this call,
|
|
|
|
_Id_ becomes invalid and all resources associated with the thread
|
|
|
|
are reclaimed. Note that threads with the attribute `detached`
|
|
|
|
`true` cannot be joined. See also current_thread/2.
|
|
|
|
|
|
|
|
A thread that has been completed without thread_join/2 being
|
|
|
|
called on it is partly reclaimed: the Prolog stacks are released and the
|
|
|
|
C-thread is destroyed. A small data-structure representing the
|
|
|
|
exit-status of the thread is retained until thread_join/2 is called on
|
|
|
|
the thread. Defined values for _Status_ are:
|
|
|
|
|
|
|
|
+ true
|
|
|
|
The goal has been proven successfully.
|
|
|
|
|
|
|
|
+ false
|
|
|
|
The goal has failed.
|
|
|
|
|
|
|
|
+ exception( _Term_)
|
|
|
|
The thread is terminated on an
|
|
|
|
exception. See print_message/2 to turn system exceptions into
|
|
|
|
readable messages.
|
|
|
|
|
|
|
|
+ exited( _Term_)
|
|
|
|
The thread is terminated on thread_exit/1 using the argument _Term_.
|
|
|
|
|
|
|
|
|
|
|
|
+ thread_detach(+ _Id_)
|
|
|
|
|
|
|
|
|
|
|
|
Switch thread into detached-state (see `detached` option at
|
|
|
|
thread_create/3 at runtime. _Id_ is the identifier of the thread
|
|
|
|
placed in detached state.
|
|
|
|
|
|
|
|
One of the possible applications is to simplify debugging. Threads that
|
|
|
|
are created as `detached` leave no traces if they crash. For
|
|
|
|
not-detached threads the status can be inspected using
|
|
|
|
current_thread/2. Threads nobody is waiting for may be created
|
|
|
|
normally and detach themselves just before completion. This way they
|
|
|
|
leave no traces on normal completion and their reason for failure can be
|
|
|
|
inspected.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2007-01-01 16:54:39 +00:00
|
|
|
thread_join(Id, Status) :-
|
|
|
|
nonvar(Status), !,
|
2010-10-12 22:02:24 +01:00
|
|
|
'$do_error'(uninstantiation_error(Status),thread_join(Id, Status)).
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_join(Id, Status) :-
|
2007-01-01 18:11:24 +00:00
|
|
|
'$check_thread_or_alias'(Id, thread_join(Id, Status)),
|
2007-01-02 03:01:20 +00:00
|
|
|
'$thread_id_alias'(Id0, Id),
|
2004-01-23 02:23:51 +00:00
|
|
|
'$thread_join'(Id0),
|
2008-08-07 21:51:23 +01:00
|
|
|
'$mk_tstatus_key'(Id0, Key),
|
|
|
|
recorded(Key, Status, R),
|
2008-08-06 18:32:22 +01:00
|
|
|
erase(R),
|
2004-02-19 19:24:46 +00:00
|
|
|
'$thread_destroy'(Id0).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2007-01-02 11:10:58 +00:00
|
|
|
thread_cancel(Id) :-
|
|
|
|
(Id == main; Id == 0), !,
|
|
|
|
'$do_error'(permission_error(cancel, thread, main), thread_cancel(Id)).
|
2007-01-01 18:11:24 +00:00
|
|
|
thread_cancel(Id) :-
|
2008-04-04 11:02:44 +01:00
|
|
|
thread_signal(Id, throw(error(thread_cancel(Id),thread_cancel(Id)))).
|
2007-01-01 18:11:24 +00:00
|
|
|
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_detach(Id) :-
|
2007-01-01 18:11:24 +00:00
|
|
|
'$check_thread_or_alias'(Id, thread_detach(Id)),
|
2007-01-02 03:01:20 +00:00
|
|
|
'$thread_id_alias'(Id0, Id),
|
2008-08-06 18:32:22 +01:00
|
|
|
'$detach_thread'(Id0),
|
2008-08-07 21:51:23 +01:00
|
|
|
'$mk_tstatus_key'(Id0, Key),
|
|
|
|
( recorded(Key, _, _) ->
|
2008-03-31 17:26:31 +01:00
|
|
|
'$erase_thread_info'(Id0),
|
|
|
|
'$thread_destroy'(Id0)
|
2008-04-08 16:36:53 +01:00
|
|
|
;
|
|
|
|
'$thread_unlock'(Id0)
|
2008-03-26 14:51:41 +00:00
|
|
|
).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_exit(+ _Term_)
|
|
|
|
|
|
|
|
|
|
|
|
Terminates the thread immediately, leaving `exited( _Term_)` as
|
|
|
|
result-state for thread_join/2. If the thread has the attribute
|
|
|
|
`detached` `true` it terminates, but its exit status cannot be
|
|
|
|
retrieved using thread_join/2 making the value of _Term_
|
|
|
|
irrelevant. The Prolog stacks and C-thread are reclaimed.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2007-01-01 17:20:56 +00:00
|
|
|
thread_exit(Term) :-
|
|
|
|
var(Term), !,
|
|
|
|
'$do_error'(instantiation_error, thread_exit(Term)).
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_exit(Term) :-
|
2012-10-22 10:18:01 +01:00
|
|
|
throw('$thread_finished'(exited(Term))).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2014-10-07 01:35:41 +01:00
|
|
|
'$run_at_thread_exit'(_Id0) :-
|
2008-08-07 21:51:23 +01:00
|
|
|
'$thread_run_at_exit'(G, M),
|
|
|
|
catch(once(M:G), _, fail),
|
2008-03-28 10:57:28 +00:00
|
|
|
fail.
|
2004-01-23 02:23:51 +00:00
|
|
|
'$run_at_thread_exit'(Id0) :-
|
2008-04-02 16:13:16 +01:00
|
|
|
recorded('$thread_exit_hook',[Id0|Hook],R), erase(R),
|
|
|
|
catch(once(Hook),_,fail),
|
2008-01-27 11:01:07 +00:00
|
|
|
fail.
|
2008-08-06 18:32:22 +01:00
|
|
|
'$run_at_thread_exit'(_).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @pred thread_at_exit(: _Term_)
|
|
|
|
|
|
|
|
|
|
|
|
Run _Goal_ just before releasing the thread resources. This is to
|
|
|
|
be compared to `at_halt/1`, but only for the current
|
|
|
|
thread. These hooks are ran regardless of why the execution of the
|
|
|
|
thread has been completed. As these hooks are run, the return-code is
|
|
|
|
already available through thread_property/2 using the result of
|
|
|
|
thread_self/1 as thread-identifier. If you want to guarantee the
|
|
|
|
execution of an exit hook no matter how the thread terminates (the thread
|
|
|
|
can be aborted before reaching the thread_at_exit/1 call), consider
|
|
|
|
using instead the `at_exit/1` option of thread_create/3.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_at_exit(Goal) :-
|
|
|
|
'$check_callable'(Goal,thread_at_exit(Goal)),
|
|
|
|
'$thread_self'(Id0),
|
|
|
|
recordz('$thread_exit_hook',[Id0|Goal],_).
|
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/**
|
|
|
|
@}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
@defgroup Monitoring_Threads Monitoring Threads
|
|
|
|
@ingroup Threads
|
|
|
|
|
|
|
|
|
|
|
|
Normal multi-threaded applications should not need these the predicates
|
|
|
|
from this section because almost any usage of these predicates is
|
|
|
|
unsafe. For example checking the existence of a thread before signalling
|
|
|
|
it is of no use as it may vanish between the two calls. Catching
|
|
|
|
exceptions using catch/3 is the only safe way to deal with
|
|
|
|
thread-existence errors.
|
|
|
|
|
|
|
|
These predicates are provided for diagnosis and monitoring tasks.
|
|
|
|
@{
|
|
|
|
*/
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred current_thread(+ _Id_, - _Status_)
|
|
|
|
|
|
|
|
|
|
|
|
Enumerates identifiers and status of all currently known threads.
|
|
|
|
Calling current_thread/2 does not influence any thread. See also
|
|
|
|
thread_join/2. For threads that have an alias-name, this name is
|
|
|
|
returned in _Id_ instead of the numerical thread identifier.
|
|
|
|
_Status_ is one of:
|
|
|
|
|
|
|
|
+ running
|
|
|
|
The thread is running. This is the initial status of a thread. Please
|
|
|
|
note that threads waiting for something are considered running too.
|
|
|
|
|
|
|
|
+ false
|
|
|
|
The _Goal_ of the thread has been completed and failed.
|
|
|
|
|
|
|
|
+ true
|
|
|
|
The _Goal_ of the thread has been completed and succeeded.
|
|
|
|
|
|
|
|
+ exited( _Term_)
|
|
|
|
The _Goal_ of the thread has been terminated using thread_exit/1
|
|
|
|
with _Term_ as argument. If the underlying native thread has
|
|
|
|
exited (using pthread_exit()) _Term_ is unbound.
|
|
|
|
|
|
|
|
+ exception( _Term_)
|
|
|
|
The _Goal_ of the thread has been terminated due to an uncaught
|
|
|
|
exception (see throw/1 and catch/3).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2007-01-02 02:51:31 +00:00
|
|
|
current_thread(Id, Status) :-
|
2013-10-29 12:43:31 +00:00
|
|
|
catch(thread_property(Id, status(Status)),
|
|
|
|
error(existence_error(_,_),_), fail).
|
2007-01-02 02:51:31 +00:00
|
|
|
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2007-01-02 03:01:20 +00:00
|
|
|
'$thread_id_alias'(Id, Alias) :-
|
2007-01-02 02:51:31 +00:00
|
|
|
recorded('$thread_alias', [Id|Alias], _), !.
|
2007-01-02 03:01:20 +00:00
|
|
|
'$thread_id_alias'(Id, Id).
|
2006-05-04 19:46:50 +01:00
|
|
|
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2007-01-28 16:00:31 +00:00
|
|
|
'$mutex_id_alias'(Id, Alias) :-
|
|
|
|
recorded('$mutex_alias', [Id|Alias], _), !.
|
|
|
|
'$mutex_id_alias'(Id, Id).
|
|
|
|
|
|
|
|
|
2007-03-18 14:15:53 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
thread_property(Prop) :-
|
|
|
|
'$check_thread_property'(Prop, thread_property(Prop)),
|
|
|
|
'$thread_self'(Id),
|
|
|
|
'$thread_property'(Prop, Id).
|
2007-03-18 14:15:53 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @pred thread_property(? _Id_, ? _Property_)
|
2007-03-18 14:15:53 +00:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
Enumerates the properties of the specified thread.
|
|
|
|
Calling thread_property/2 does not influence any thread. See also
|
|
|
|
thread_join/2. For threads that have an alias-name, this name can
|
|
|
|
be used in _Id_ instead of the numerical thread identifier.
|
|
|
|
_Property_ is one of:
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
+ status( _Status_)
|
|
|
|
The thread status of a thread (see below).
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
+ alias( _Alias_)
|
|
|
|
The thread alias, if it exists.
|
2007-01-28 16:00:31 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
+ at_exit( _AtExit_)
|
|
|
|
The thread exit hook, if defined (not available if the thread is already terminated).
|
2007-01-28 16:00:31 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
+ detached( _Boolean_)
|
|
|
|
The detached state of the thread.
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
+ stack( _Size_)
|
|
|
|
The thread stack data-area size.
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
+ trail( _Size_)
|
|
|
|
The thread trail data-area size.
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
+ system( _Size_)
|
|
|
|
The thread system data-area size.
|
2014-09-11 20:06:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2014-09-15 09:13:50 +01:00
|
|
|
thread_property(Id, Prop) :-
|
|
|
|
( nonvar(Id) ->
|
|
|
|
'$check_thread_or_alias'(Id, thread_property(Id, Prop))
|
|
|
|
; '$enumerate_threads'(Id)
|
|
|
|
),
|
|
|
|
'$check_thread_property'(Prop, thread_property(Id, Prop)),
|
|
|
|
'$thread_id_alias'(Id0, Id),
|
|
|
|
'$thread_property'(Prop, Id0).
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
'$enumerate_threads'(Id) :-
|
|
|
|
'$max_threads'(Max),
|
|
|
|
Max1 is Max-1,
|
|
|
|
between(0,Max1,Id),
|
|
|
|
'$thread_stacks'(Id, _, _, _).
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
'$thread_property'(alias(Alias), Id) :-
|
|
|
|
recorded('$thread_alias', [Id|Alias], _).
|
|
|
|
'$thread_property'(status(Status), Id) :-
|
|
|
|
'$mk_tstatus_key'(Id, Key),
|
|
|
|
( recorded(Key, Exit, _) ->
|
|
|
|
Status = Exit
|
|
|
|
; Status = running
|
|
|
|
).
|
|
|
|
'$thread_property'(detached(Detached), Id) :-
|
|
|
|
( '$thread_detached'(Id,Detached) -> true ; Detached = false ).
|
2014-10-07 01:35:41 +01:00
|
|
|
'$thread_property'(at_exit(M:G), _Id) :-
|
2014-09-15 09:13:50 +01:00
|
|
|
'$thread_run_at_exit'(G,M).
|
|
|
|
'$thread_property'(stack(Stack), Id) :-
|
|
|
|
'$thread_stacks'(Id, Stack, _, _).
|
|
|
|
'$thread_property'(trail(Trail), Id) :-
|
|
|
|
'$thread_stacks'(Id, _, Trail, _).
|
|
|
|
'$thread_property'(system(System), Id) :-
|
|
|
|
'$thread_stacks'(Id, _, _, System).
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
threads :-
|
|
|
|
format(user_error,'------------------------------------------------------------------------~n',[]),
|
|
|
|
format(user_error, '~t~a~48+~n', 'Thread Detached Status'),
|
|
|
|
format(user_error,'------------------------------------------------------------------------~n',[]),
|
|
|
|
thread_property(Id, detached(Detached)),
|
|
|
|
thread_property(Id, status(Status)),
|
|
|
|
'$thread_id_alias'(Id, Alias),
|
|
|
|
format(user_error,'~t~q~30+~33|~w~42|~q~n', [Alias, Detached, Status]),
|
|
|
|
fail.
|
|
|
|
threads :-
|
|
|
|
format(user_error,'------------------------------------------------------------------------~n',[]).
|
|
|
|
|
|
|
|
|
|
|
|
'$check_thread_or_alias'(Term, Goal) :-
|
|
|
|
var(Term), !,
|
|
|
|
'$do_error'(instantiation_error, Goal).
|
|
|
|
'$check_thread_or_alias'(Term, Goal) :-
|
|
|
|
\+ integer(Term), \+ atom(Term), !,
|
|
|
|
'$do_error'(domain_error(thread_or_alias, Term), Goal).
|
|
|
|
'$check_thread_or_alias'(Term, Goal) :-
|
|
|
|
atom(Term), \+ recorded('$thread_alias',[_|Term],_), !,
|
|
|
|
'$do_error'(existence_error(thread, Term), Goal).
|
|
|
|
'$check_thread_or_alias'(Term, Goal) :-
|
|
|
|
integer(Term), \+ '$valid_thread'(Term), !,
|
|
|
|
'$do_error'(existence_error(thread, Term), Goal).
|
|
|
|
'$check_thread_or_alias'(_,_).
|
|
|
|
|
|
|
|
'$check_thread_property'(Term, _) :-
|
|
|
|
var(Term), !.
|
|
|
|
'$check_thread_property'(alias(_), _) :- !.
|
|
|
|
'$check_thread_property'(detached(_), _) :- !.
|
|
|
|
'$check_thread_property'(at_exit(_), _) :- !.
|
|
|
|
'$check_thread_property'(status(_), _) :- !.
|
|
|
|
'$check_thread_property'(stack(_), _) :- !.
|
|
|
|
'$check_thread_property'(trail(_), _) :- !.
|
|
|
|
'$check_thread_property'(system(_), _) :- !.
|
|
|
|
'$check_thread_property'(Term, Goal) :-
|
|
|
|
'$do_error'(domain_error(thread_property, Term), Goal).
|
|
|
|
|
|
|
|
'$check_mutex_or_alias'(Term, Goal) :-
|
|
|
|
var(Term), !,
|
|
|
|
'$do_error'(instantiation_error, Goal).
|
|
|
|
'$check_mutex_or_alias'(Term, Goal) :-
|
|
|
|
\+ integer(Term), \+ atom(Term), !,
|
|
|
|
'$do_error'(domain_error(mutex_or_alias, Term), Goal).
|
|
|
|
'$check_mutex_or_alias'(Term, Goal) :-
|
|
|
|
atom(Term), \+ recorded('$mutex_alias',[_|Term],_), !,
|
|
|
|
'$do_error'(existence_error(mutex, Term), Goal).
|
|
|
|
'$check_mutex_or_alias'(Term, Goal) :-
|
|
|
|
% integer(Term), \+ '$valid_mutex'(Term), !,
|
|
|
|
integer(Term), \+ recorded('$mutex_alias',[Term|_],_), !,
|
|
|
|
'$do_error'(existence_error(mutex, Term), Goal).
|
|
|
|
'$check_mutex_or_alias'(_,_).
|
|
|
|
|
|
|
|
'$check_mutex_property'(Term, _) :-
|
|
|
|
var(Term), !.
|
|
|
|
'$check_mutex_property'(alias(_), _) :- !.
|
|
|
|
'$check_mutex_property'(status(Status), Goal) :- !,
|
|
|
|
( var(Status) ->
|
|
|
|
true
|
|
|
|
; Status = unlocked ->
|
|
|
|
true
|
|
|
|
; Status = locked(_, _) ->
|
|
|
|
true
|
|
|
|
; '$do_error'(domain_error(mutex_property, status(Status)), Goal)
|
|
|
|
).
|
|
|
|
'$check_mutex_property'(Term, Goal) :-
|
|
|
|
'$do_error'(domain_error(mutex_property, Term), Goal).
|
|
|
|
|
|
|
|
'$mk_tstatus_key'(Id0, Key) :-
|
|
|
|
atomic_concat('$thread_exit_status__',Id0,Key).
|
|
|
|
|
|
|
|
/** @pred thread_statistics(+ _Id_, + _Key_, - _Value_)
|
|
|
|
|
|
|
|
|
|
|
|
Obtains statistical information on thread _Id_ as `statistics/2`
|
|
|
|
does in single-threaded applications. This call returns all keys
|
|
|
|
of `statistics/2`, although only information statistics about the
|
|
|
|
stacks and CPU time yield different values for each thread.
|
|
|
|
|
|
|
|
+ mutex_statistics
|
|
|
|
|
|
|
|
|
|
|
|
Print usage statistics on internal mutexes and mutexes associated
|
|
|
|
with dynamic predicates. For each mutex two numbers are printed:
|
|
|
|
the number of times the mutex was acquired and the number of
|
|
|
|
collisions: the number times the calling thread has to
|
|
|
|
wait for the mutex. The collision-count is not available on
|
|
|
|
Windows as this would break portability to Windows-95/98/ME or
|
|
|
|
significantly harm performance. Generally collision count is
|
|
|
|
close to zero on single-CPU hardware.
|
|
|
|
|
|
|
|
+ threads
|
|
|
|
|
|
|
|
|
|
|
|
Prints a table of current threads and their status.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
thread_statistics(Id, Key, Val) :-
|
2014-10-07 01:35:41 +01:00
|
|
|
format("not implemented yet: ~w, ~w, ~w~n",[Id, Key, Val]).
|
2014-09-15 09:13:50 +01:00
|
|
|
|
|
|
|
%% @}
|
|
|
|
|
|
|
|
|
|
|
|
/** @defgroup Signalling_Threads Signalling Threads
|
|
|
|
@ingroup Threadas
|
|
|
|
|
|
|
|
|
|
|
|
These predicates provide a mechanism to make another thread execute some
|
|
|
|
goal as an <em>interrupt</em>. Signalling threads is safe as these
|
|
|
|
interrupts are only checked at safe points in the virtual machine.
|
|
|
|
Nevertheless, signalling in multi-threaded environments should be
|
|
|
|
handled with care as the receiving thread may hold a <em>mutex</em>
|
|
|
|
(see with_mutex/2). Signalling probably only makes sense to start
|
|
|
|
debugging threads and to cancel no-longer-needed threads with throw/1,
|
|
|
|
where the receiving thread should be designed carefully do handle
|
|
|
|
exceptions at any point.
|
|
|
|
|
|
|
|
@}
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @defgroup Thread_Synchronisation Thread Synchronisation
|
|
|
|
@ingroup Threads
|
|
|
|
@{
|
|
|
|
|
|
|
|
All
|
|
|
|
internal Prolog operations are thread-safe. This implies two Prolog
|
|
|
|
threads can operate on the same dynamic predicate without corrupting the
|
|
|
|
consistency of the predicate. This section deals with user-level
|
|
|
|
<em>mutexes</em> (called <em>monitors</em> in ADA or
|
|
|
|
<em>critical-sections</em> by Microsoft). A mutex is a
|
|
|
|
<em>MUT</em>ual <em>EX</em>clusive device, which implies at most one thread
|
|
|
|
can <em>hold</em> a mutex.
|
|
|
|
|
|
|
|
Mutexes are used to realise related updates to the Prolog database.
|
|
|
|
With `related', we refer to the situation where a `transaction' implies
|
|
|
|
two or more changes to the Prolog database. For example, we have a
|
|
|
|
predicate `address/2`, representing the address of a person and we want
|
|
|
|
to change the address by retracting the old and asserting the new
|
|
|
|
address. Between these two operations the database is invalid: this
|
|
|
|
person has either no address or two addresses, depending on the
|
|
|
|
assert/retract order.
|
|
|
|
|
|
|
|
Here is how to realise a correct update:
|
|
|
|
|
|
|
|
~~~~~
|
|
|
|
:- initialization
|
|
|
|
mutex_create(addressbook).
|
|
|
|
|
|
|
|
change_address(Id, Address) :-
|
|
|
|
mutex_lock(addressbook),
|
|
|
|
retractall(address(Id, _)),
|
|
|
|
asserta(address(Id, Address)),
|
|
|
|
mutex_unlock(addressbook).
|
|
|
|
~~~~~
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @pred mutex_create(? _MutexId_)
|
|
|
|
|
|
|
|
|
|
|
|
Create a mutex. if _MutexId_ is an atom, a <em>named</em> mutex is
|
|
|
|
created. If it is a variable, an anonymous mutex reference is returned.
|
|
|
|
There is no limit to the number of mutexes that can be created.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
mutex_create(Mutex) :-
|
|
|
|
( atom(Mutex) ->
|
|
|
|
mutex_create(_, [alias(Mutex)])
|
|
|
|
; mutex_create(Mutex, [])
|
|
|
|
).
|
|
|
|
|
|
|
|
mutex_create(Id, Options) :-
|
|
|
|
nonvar(Id), !,
|
|
|
|
'$do_error'(uninstantiation_error(Id), mutex_create(Id, Options)).
|
|
|
|
mutex_create(Id, Options) :-
|
|
|
|
Goal = mutex_create(Id, Options),
|
|
|
|
'$mutex_options'(Options, Alias, Goal),
|
|
|
|
( atom(Alias) ->
|
|
|
|
( recorded('$mutex_alias',[_| Alias], _) ->
|
|
|
|
'$do_error'(permission_error(create, mutex, Alias), Goal)
|
|
|
|
; '$new_mutex'(Id),
|
|
|
|
recorda('$mutex_alias', [Id| Alias], _)
|
|
|
|
)
|
|
|
|
; '$new_mutex'(Id),
|
|
|
|
recorda('$mutex_alias', [Id| Id], _)
|
|
|
|
).
|
|
|
|
|
|
|
|
'$mutex_options'(Var, _, Goal) :-
|
|
|
|
var(Var), !,
|
|
|
|
'$do_error'(instantiation_error, Goal).
|
|
|
|
'$mutex_options'([], _, _) :- !.
|
|
|
|
'$mutex_options'([Option| Options], Alias, Goal) :- !,
|
|
|
|
'$mutex_option'(Option, Alias, Goal),
|
|
|
|
'$mutex_options'(Options, Alias, Goal).
|
|
|
|
'$mutex_options'(Options, _, Goal) :-
|
|
|
|
'$do_error'(type_error(list, Options), Goal).
|
|
|
|
|
|
|
|
'$mutex_option'(Var, _, Goal) :-
|
|
|
|
var(Var), !,
|
|
|
|
'$do_error'(instantiation_error, Goal).
|
|
|
|
'$mutex_option'(alias(Alias), Alias, Goal) :- !,
|
|
|
|
( atom(Alias) ->
|
|
|
|
true
|
|
|
|
; '$do_error'(type_error(atom, Alias), Goal)
|
|
|
|
).
|
|
|
|
'$mutex_option'(Option, _, Goal) :-
|
|
|
|
'$do_error'(domain_error(mutex_option, Option), Goal).
|
|
|
|
|
|
|
|
/*
|
|
|
|
mutex_create(V) :-
|
|
|
|
var(V), !,
|
|
|
|
'$new_mutex'(V),
|
|
|
|
recorda('$mutex_alias',[V|V],_).
|
|
|
|
mutex_create(A) :-
|
|
|
|
atom(A),
|
|
|
|
recorded('$mutex_alias',[_|A],_), !,
|
|
|
|
'$do_error'(permission_error(create,mutex,A),mutex_create(A)).
|
|
|
|
mutex_create(A) :-
|
|
|
|
atom(A), !,
|
|
|
|
'$new_mutex'(Id),
|
|
|
|
recorda('$mutex_alias',[Id|A],_).
|
|
|
|
mutex_create(V) :-
|
|
|
|
'$do_error'(type_error(atom,V),mutex_create(V)).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/** @pred mutex_destroy(+ _MutexId_)
|
|
|
|
|
|
|
|
|
|
|
|
Destroy a mutex. After this call, _MutexId_ becomes invalid and
|
|
|
|
further references yield an `existence_error` exception.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
mutex_destroy(Mutex) :-
|
|
|
|
'$check_mutex_or_alias'(Mutex, mutex_destroy(Mutex)),
|
|
|
|
'$mutex_id_alias'(Id, Mutex),
|
|
|
|
'$destroy_mutex'(Id),
|
|
|
|
'$erase_mutex_info'(Id).
|
|
|
|
|
|
|
|
'$erase_mutex_info'(Id) :-
|
|
|
|
recorded('$mutex_alias',[Id|_],R),
|
|
|
|
erase(R),
|
|
|
|
fail.
|
|
|
|
'$erase_mutex_info'(_).
|
|
|
|
|
|
|
|
/** @pred mutex_lock(+ _MutexId_)
|
|
|
|
|
|
|
|
|
|
|
|
Lock the mutex. Prolog mutexes are <em>recursive</em> mutexes: they
|
|
|
|
can be locked multiple times by the same thread. Only after unlocking
|
|
|
|
it as many times as it is locked, the mutex becomes available for
|
|
|
|
locking by other threads. If another thread has locked the mutex the
|
|
|
|
calling thread is suspended until to mutex is unlocked.
|
|
|
|
|
|
|
|
If _MutexId_ is an atom, and there is no current mutex with that
|
|
|
|
name, the mutex is created automatically using mutex_create/1. This
|
|
|
|
implies named mutexes need not be declared explicitly.
|
|
|
|
|
|
|
|
Please note that locking and unlocking mutexes should be paired
|
|
|
|
carefully. Especially make sure to unlock mutexes even if the protected
|
|
|
|
code fails or raises an exception. For most common cases use
|
|
|
|
with_mutex/2, which provides a safer way for handling Prolog-level
|
|
|
|
mutexes.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
mutex_lock(V) :-
|
|
|
|
var(V), !,
|
|
|
|
'$do_error'(instantiation_error,mutex_lock(V)).
|
|
|
|
mutex_lock(A) :-
|
|
|
|
recorded('$mutex_alias',[Id|A],_), !,
|
|
|
|
'$lock_mutex'(Id).
|
|
|
|
mutex_lock(A) :-
|
|
|
|
atom(A), !,
|
|
|
|
mutex_create(A),
|
|
|
|
mutex_lock(A).
|
|
|
|
mutex_lock(V) :-
|
|
|
|
'$do_error'(type_error(atom,V),mutex_lock(V)).
|
|
|
|
|
|
|
|
/** @pred mutex_trylock(+ _MutexId_)
|
|
|
|
|
|
|
|
|
|
|
|
As mutex_lock/1, but if the mutex is held by another thread, this
|
|
|
|
predicates fails immediately.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
mutex_trylock(V) :-
|
|
|
|
var(V), !,
|
2004-02-21 20:25:45 +00:00
|
|
|
'$do_error'(instantiation_error,mutex_trylock(V)).
|
2004-01-23 02:23:51 +00:00
|
|
|
mutex_trylock(A) :-
|
2007-01-28 16:00:31 +00:00
|
|
|
recorded('$mutex_alias',[Id|A],_), !,
|
2004-01-23 02:23:51 +00:00
|
|
|
'$trylock_mutex'(Id).
|
|
|
|
mutex_trylock(A) :-
|
|
|
|
atom(A), !,
|
|
|
|
mutex_create(A),
|
|
|
|
mutex_trylock(A).
|
|
|
|
mutex_trylock(V) :-
|
|
|
|
'$do_error'(type_error(atom,V),mutex_trylock(V)).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred mutex_unlock(+ _MutexId_)
|
|
|
|
|
|
|
|
|
|
|
|
Unlock the mutex. This can only be called if the mutex is held by the
|
|
|
|
calling thread. If this is not the case, a `permission_error`
|
|
|
|
exception is raised.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2007-01-28 16:00:31 +00:00
|
|
|
mutex_unlock(Mutex) :-
|
|
|
|
'$check_mutex_or_alias'(Mutex, mutex_unlock(Mutex)),
|
|
|
|
'$mutex_id_alias'(Id, Mutex),
|
2004-01-23 02:23:51 +00:00
|
|
|
( '$unlock_mutex'(Id) ->
|
|
|
|
true
|
|
|
|
;
|
2007-01-28 16:00:31 +00:00
|
|
|
'$do_error'(permission_error(unlock,mutex,Mutex),mutex_unlock(Mutex))
|
2004-01-23 02:23:51 +00:00
|
|
|
).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred mutex_unlock_all
|
|
|
|
|
|
|
|
|
|
|
|
Unlock all mutexes held by the current thread. This call is especially
|
|
|
|
useful to handle thread-termination using abort/0 or exceptions. See
|
|
|
|
also thread_signal/2.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2004-01-23 02:23:51 +00:00
|
|
|
mutex_unlock_all :-
|
2007-01-01 18:11:24 +00:00
|
|
|
'$thread_self'(Tid),
|
|
|
|
'$unlock_all_thread_mutexes'(Tid).
|
|
|
|
|
|
|
|
'$unlock_all_thread_mutexes'(Tid) :-
|
2007-01-28 16:00:31 +00:00
|
|
|
recorded('$mutex_alias',[Id|_],_),
|
2007-01-01 18:11:24 +00:00
|
|
|
'$mutex_info'(Id, NRefs, Tid),
|
2004-01-23 02:23:51 +00:00
|
|
|
NRefs > 0,
|
|
|
|
'$mutex_unlock_all'(Id),
|
|
|
|
fail.
|
2007-01-01 18:11:24 +00:00
|
|
|
'$unlock_all_thread_mutexes'(_).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
|
|
|
'$mutex_unlock_all'(Id) :-
|
|
|
|
'$mutex_info'(Id, NRefs, _),
|
|
|
|
NRefs > 0,
|
|
|
|
'$unlock_mutex'(Id),
|
|
|
|
'$mutex_unlock_all'(Id).
|
2006-12-30 10:49:37 +00:00
|
|
|
|
2014-09-15 19:10:49 +01:00
|
|
|
/** @pred with_mutex(+ _MutexId_, : _Goal_)
|
|
|
|
|
|
|
|
|
|
|
|
Execute _Goal_ while holding _MutexId_. If _Goal_ leaves
|
|
|
|
choicepoints, these are destroyed (as in once/1). The mutex is unlocked
|
|
|
|
regardless of whether _Goal_ succeeds, fails or raises an exception.
|
|
|
|
An exception thrown by _Goal_ is re-thrown after the mutex has been
|
|
|
|
successfully unlocked. See also `mutex_create/2`.
|
|
|
|
|
|
|
|
Although described in the thread-section, this predicate is also
|
|
|
|
available in the single-threaded version, where it behaves simply as
|
|
|
|
once/1.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2010-06-17 00:26:41 +01:00
|
|
|
with_mutex(M, G) :-
|
2014-05-30 01:00:57 +01:00
|
|
|
( '$no_threads' ->
|
|
|
|
once(G)
|
|
|
|
;
|
|
|
|
var(M) ->
|
|
|
|
'$do_error'(instantiation_error,with_mutex(M, G))
|
|
|
|
;
|
|
|
|
var(G) ->
|
|
|
|
'$do_error'(instantiation_error,with_mutex(M, G))
|
|
|
|
;
|
|
|
|
\+ callable(G) ->
|
|
|
|
'$do_error'(type_error(callable,G),with_mutex(M, G))
|
|
|
|
;
|
|
|
|
atom(M) ->
|
2014-10-14 15:53:24 +01:00
|
|
|
'$with_with_mutex'(WMId),
|
2014-05-30 01:00:57 +01:00
|
|
|
( recorded('$mutex_alias',[Id|M],_) ->
|
2006-12-30 10:49:37 +00:00
|
|
|
true
|
2014-05-30 01:00:57 +01:00
|
|
|
; '$new_mutex'(Id),
|
2007-01-28 16:00:31 +00:00
|
|
|
recorda('$mutex_alias',[Id|M],_)
|
2014-05-30 01:00:57 +01:00
|
|
|
),
|
|
|
|
'$lock_mutex'(Id),
|
2014-10-14 15:53:24 +01:00
|
|
|
'$unlock_with_mutex'(WMId),
|
2014-05-30 01:00:57 +01:00
|
|
|
( catch('$execute'(G), E, ('$unlock_mutex'(Id), throw(E))) ->
|
2007-01-08 11:30:38 +00:00
|
|
|
'$unlock_mutex'(Id)
|
2014-05-30 01:00:57 +01:00
|
|
|
; '$unlock_mutex'(Id),
|
2007-01-08 11:30:38 +00:00
|
|
|
fail
|
2014-05-30 01:00:57 +01:00
|
|
|
)
|
|
|
|
;
|
|
|
|
'$do_error'(type_error(atom,M),with_mutex(M, G))
|
2007-01-08 11:30:38 +00:00
|
|
|
).
|
2006-12-30 10:49:37 +00:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred current_mutex(? _MutexId_, ? _ThreadId_, ? _Count_)
|
|
|
|
|
|
|
|
|
|
|
|
Enumerates all existing mutexes. If the mutex is held by some thread,
|
|
|
|
_ThreadId_ is unified with the identifier of the holding thread and
|
|
|
|
_Count_ with the recursive count of the mutex. Otherwise,
|
|
|
|
_ThreadId_ is `[]` and _Count_ is 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2004-01-23 02:23:51 +00:00
|
|
|
current_mutex(M, T, NRefs) :-
|
2007-01-28 16:00:31 +00:00
|
|
|
recorded('$mutex_alias',[Id|M],_),
|
2004-01-23 02:23:51 +00:00
|
|
|
'$mutex_info'(Id, NRefs, T).
|
|
|
|
|
2007-03-16 09:38:48 +00:00
|
|
|
mutex_property(Mutex, Prop) :-
|
|
|
|
( nonvar(Mutex) ->
|
|
|
|
'$check_mutex_or_alias'(Mutex, mutex_property(Mutex, Prop))
|
|
|
|
; recorded('$mutex_alias', [_|Mutex], _)
|
|
|
|
),
|
|
|
|
'$check_mutex_property'(Prop, mutex_property(Mutex, Prop)),
|
|
|
|
'$mutex_id_alias'(Id, Mutex),
|
|
|
|
'$mutex_property'(Id, Prop).
|
|
|
|
|
|
|
|
'$mutex_property'(Id, alias(Alias)) :-
|
2007-03-18 14:15:53 +00:00
|
|
|
recorded('$mutex_alias', [Id|Alias], _),
|
|
|
|
Id \= Alias.
|
2007-10-22 13:58:27 +01:00
|
|
|
'$mutex_property'(Id, status(Status)) :-
|
2007-03-18 13:10:23 +00:00
|
|
|
'$mutex_info'(Id, Count, HoldingThread),
|
2007-10-22 13:58:27 +01:00
|
|
|
( Count =:= 0 ->
|
|
|
|
Status = unlocked
|
|
|
|
; % Count > 0,
|
|
|
|
'$thread_id_alias'(HoldingThread, Alias),
|
|
|
|
once((Thread = Alias; Thread = HoldingThread)),
|
|
|
|
Status = locked(Thread, Count)
|
|
|
|
).
|
2008-03-31 19:41:36 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
%% @}
|
|
|
|
|
|
|
|
/** @defgroup Thread_Communication Thread communication
|
|
|
|
@ingroup Threads
|
|
|
|
|
|
|
|
|
|
|
|
Prolog threads can exchange data using dynamic predicates, database
|
|
|
|
records, and other globally shared data. These provide no suitable means
|
|
|
|
to wait for data or a condition as they can only be checked in an
|
|
|
|
expensive polling loop. <em>Message queues</em> provide a means for
|
|
|
|
threads to wait for data or conditions without using the CPU.
|
|
|
|
|
|
|
|
Each thread has a message-queue attached to it that is identified
|
|
|
|
by the thread. Additional queues are created using
|
|
|
|
`message_queue_create/2`.
|
|
|
|
@{
|
|
|
|
*/
|
2008-03-31 19:41:36 +01:00
|
|
|
|
2007-10-22 13:58:27 +01:00
|
|
|
message_queue_create(Id, Options) :-
|
|
|
|
nonvar(Id), !,
|
2010-10-12 22:02:24 +01:00
|
|
|
'$do_error'(uninstantiation_error(Id), message_queue_create(Id, Options)).
|
2007-10-22 13:58:27 +01:00
|
|
|
message_queue_create(Id, Options) :-
|
|
|
|
var(Options), !,
|
|
|
|
'$do_error'(instantiation_error, message_queue_create(Id, Options)).
|
2008-02-09 13:04:32 +00:00
|
|
|
message_queue_create(Id, []) :- !,
|
2014-10-14 15:53:24 +01:00
|
|
|
'$message_queue_create'(Id).
|
2007-10-22 13:58:27 +01:00
|
|
|
message_queue_create(Id, [alias(Alias)]) :-
|
|
|
|
var(Alias), !,
|
2008-02-09 13:04:32 +00:00
|
|
|
'$do_error'(instantiation_error, message_queue_create(Id, [alias(Alias)])).
|
2007-10-22 13:58:27 +01:00
|
|
|
message_queue_create(Id, [alias(Alias)]) :-
|
2008-02-09 13:04:32 +00:00
|
|
|
\+ atom(Alias), !,
|
|
|
|
'$do_error'(type_error(atom,Alias), message_queue_create(Id, [alias(Alias)])).
|
2014-10-14 15:53:24 +01:00
|
|
|
message_queue_create(Id, [alias(Alias)]) :- var(Id), !,
|
|
|
|
( recorded('$thread_alias', [_|Alias], _) ->
|
|
|
|
'$do_error'(permission_error(create,queue,alias(Alias)),message_queue_create(Alias, [alias(Alias)]))
|
|
|
|
; '$message_queue_create'(Id),
|
|
|
|
recordz('$thread_alias', [Id|Alias], _)
|
|
|
|
).
|
2014-10-13 12:34:52 +01:00
|
|
|
message_queue_create(Alias, [alias(Alias)]) :- !,
|
|
|
|
( recorded('$thread_alias', [_|Alias], _) ->
|
|
|
|
'$do_error'(permission_error(create,queue,alias(Alias)),message_queue_create(Alias, [alias(Alias)]))
|
|
|
|
; '$message_queue_create'(Alias)
|
2008-02-09 13:04:32 +00:00
|
|
|
).
|
2008-02-24 12:28:05 +00:00
|
|
|
message_queue_create(Id, [Option| _]) :-
|
|
|
|
'$do_error'(domain_error(queue_option, Option), message_queue_create(Id, [Option| _])).
|
|
|
|
message_queue_create(Id, Options) :-
|
|
|
|
'$do_error'(type_error(list, Options), message_queue_create(Id, Options)).
|
2007-10-22 13:58:27 +01:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred message_queue_create(? _Queue_)
|
|
|
|
|
|
|
|
|
|
|
|
If _Queue_ is an atom, create a named queue. To avoid ambiguity
|
|
|
|
on `thread_send_message/2`, the name of a queue may not be in use
|
|
|
|
as a thread-name. If _Queue_ is unbound an anonymous queue is
|
|
|
|
created and _Queue_ is unified to its identifier.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2007-10-22 13:58:27 +01:00
|
|
|
message_queue_create(Id) :-
|
2008-02-09 13:04:32 +00:00
|
|
|
( var(Id) -> % ISO DTR
|
2014-10-13 12:34:52 +01:00
|
|
|
'$message_queue_create'(Id)
|
2008-02-09 13:04:32 +00:00
|
|
|
; atom(Id) -> % old behavior
|
2014-10-13 12:34:52 +01:00
|
|
|
'$message_queue_create'(Id)
|
2010-10-12 22:02:24 +01:00
|
|
|
; '$do_error'(uninstantiation_error(Id), message_queue_create(Id))
|
2007-10-22 13:58:27 +01:00
|
|
|
).
|
2008-03-31 19:41:36 +01:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred message_queue_destroy(+ _Queue_)
|
|
|
|
|
|
|
|
|
|
|
|
Destroy a message queue created with message_queue_create/1. It is
|
|
|
|
<em>not</em> allows to destroy the queue of a thread. Neither is it
|
|
|
|
allowed to destroy a queue other threads are waiting for or, for
|
|
|
|
anonymous message queues, may try to wait for later.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2004-01-23 02:23:51 +00:00
|
|
|
message_queue_destroy(Name) :-
|
|
|
|
var(Name), !,
|
2004-02-11 01:20:56 +00:00
|
|
|
'$do_error'(instantiation_error,message_queue_destroy(Name)).
|
2008-08-07 21:51:23 +01:00
|
|
|
message_queue_destroy(Name) :-
|
|
|
|
'$message_queue_destroy'(Name),
|
|
|
|
fail.
|
|
|
|
message_queue_destroy(_).
|
|
|
|
|
2014-10-14 15:53:24 +01:00
|
|
|
/* @pred message_queue_property(+ _Queue_)
|
2008-03-31 19:41:36 +01:00
|
|
|
|
|
|
|
|
2014-10-14 15:53:24 +01:00
|
|
|
Report on the alias and number of messages stored in a queue created
|
|
|
|
with message_queue_create/1.
|
|
|
|
|
|
|
|
+ `alias(Alias)` report the alias for stream _S_. It can also be used
|
|
|
|
to enumerate all message queues that have aliases, including anonymous
|
|
|
|
queues.
|
|
|
|
|
|
|
|
+ `size(Size)` unifies _Size_ with the number of messages in the queue.
|
|
|
|
*/
|
|
|
|
|
|
|
|
message_queue_property( Id, alias(Alias) ) :-
|
|
|
|
recorded('$thread_alias',[Id|Alias],_).
|
|
|
|
message_queue_property( Alias, size(Size) ) :-
|
|
|
|
ground(Alias),
|
|
|
|
recorded('$thread_alias',[Id|Alias],_),
|
|
|
|
'$message_queue_size'(Id, Size).
|
|
|
|
message_queue_property( Id, size(Size) ) :-
|
|
|
|
'$message_queue_size'(Id, Size).
|
|
|
|
|
2008-03-31 19:41:36 +01:00
|
|
|
|
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @pred thread_send_message(+ _Term_)
|
|
|
|
|
|
|
|
Places _Term_ in the message-queue of the thread running the goal.
|
|
|
|
Any term can be placed in a message queue, but note that the term is
|
|
|
|
copied to the receiving thread and variable-bindings are thus lost.
|
|
|
|
This call returns immediately.
|
|
|
|
*/
|
2007-09-18 18:50:12 +01:00
|
|
|
thread_send_message(Term) :-
|
|
|
|
'$thread_self'(Id),
|
|
|
|
thread_send_message(Id, Term).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_send_message(+ _QueueOrThreadId_, + _Term_)
|
|
|
|
|
|
|
|
Place _Term_ in the given queue or default queue of the indicated
|
|
|
|
thread (which can even be the message queue of itself (see
|
|
|
|
thread_self/1). Any term can be placed in a message queue, but note that
|
|
|
|
the term is copied to the receiving thread and variable-bindings are
|
|
|
|
thus lost. This call returns immediately.
|
|
|
|
|
|
|
|
If more than one thread is waiting for messages on the given queue and
|
|
|
|
at least one of these is waiting with a partially instantiated
|
|
|
|
_Term_, the waiting threads are <em>all</em> sent a wakeup signal,
|
|
|
|
starting a rush for the available messages in the queue. This behaviour
|
|
|
|
can seriously harm performance with many threads waiting on the same
|
|
|
|
queue as all-but-the-winner perform a useless scan of the queue. If
|
|
|
|
there is only one waiting thread or all waiting threads wait with an
|
|
|
|
unbound variable an arbitrary thread is restarted to scan the queue.
|
|
|
|
|
|
|
|
*/
|
2006-05-25 11:46:23 +01:00
|
|
|
thread_send_message(Queue, Term) :- var(Queue), !,
|
|
|
|
'$do_error'(instantiation_error,thread_send_message(Queue,Term)).
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_send_message(Queue, Term) :-
|
2014-10-13 12:34:52 +01:00
|
|
|
recorded('$thread_alias',[Id|Queue],_R), !,
|
|
|
|
'$message_queue_send'(Id, Term).
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_send_message(Queue, Term) :-
|
2014-10-13 12:34:52 +01:00
|
|
|
'$message_queue_send'(Queue, Term).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_get_message(? _Term_)
|
|
|
|
|
|
|
|
|
|
|
|
Examines the thread message-queue and if necessary blocks execution
|
|
|
|
until a term that unifies to _Term_ arrives in the queue. After
|
|
|
|
a term from the queue has been unified unified to _Term_, the
|
|
|
|
term is deleted from the queue and this predicate returns.
|
|
|
|
|
|
|
|
Please note that not-unifying messages remain in the queue. After
|
|
|
|
the following has been executed, thread 1 has the term `gnu`
|
|
|
|
in its queue and continues execution using _A_ is `gnat`.
|
|
|
|
|
|
|
|
~~~~~
|
|
|
|
<thread 1>
|
|
|
|
thread_get_message(a(A)),
|
|
|
|
|
|
|
|
<thread 2>
|
|
|
|
thread_send_message(b(gnu)),
|
|
|
|
thread_send_message(a(gnat)),
|
|
|
|
~~~~~
|
|
|
|
|
|
|
|
See also thread_peek_message/1.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_get_message(Term) :-
|
|
|
|
'$thread_self'(Id),
|
|
|
|
thread_get_message(Id, Term).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_get_message(+ _Queue_, ? _Term_)
|
|
|
|
|
|
|
|
As thread_get_message/1, operating on a given queue. It is allowed to
|
|
|
|
peek into another thread's message queue, an operation that can be used
|
|
|
|
to check whether a thread has swallowed a message sent to it.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2006-05-25 11:46:23 +01:00
|
|
|
thread_get_message(Queue, Term) :- var(Queue), !,
|
|
|
|
'$do_error'(instantiation_error,thread_get_message(Queue,Term)).
|
2004-06-29 20:04:46 +01:00
|
|
|
thread_get_message(Queue, Term) :-
|
2014-10-13 12:34:52 +01:00
|
|
|
recorded('$thread_alias',[Id|Queue],_R), !,
|
|
|
|
'$message_queue_receive'(Id, Term).
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_get_message(Queue, Term) :-
|
2014-10-13 12:34:52 +01:00
|
|
|
'$message_queue_receive'(Queue, Term).
|
2004-01-23 02:23:51 +00:00
|
|
|
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_peek_message(? _Term_)
|
|
|
|
|
|
|
|
|
|
|
|
Examines the thread message-queue and compares the queued terms
|
|
|
|
with _Term_ until one unifies or the end of the queue has been
|
|
|
|
reached. In the first case the call succeeds (possibly instantiating
|
|
|
|
_Term_. If no term from the queue unifies this call fails.
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2004-01-23 02:23:51 +00:00
|
|
|
thread_peek_message(Term) :-
|
|
|
|
'$thread_self'(Id),
|
|
|
|
thread_peek_message(Id, Term).
|
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_peek_message(+ _Queue_, ? _Term_)
|
|
|
|
|
|
|
|
As thread_peek_message/1, operating on a given queue. It is allowed to
|
|
|
|
peek into another thread's message queue, an operation that can be used
|
|
|
|
to check whether a thread has swallowed a message sent to it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Explicit message queues are designed with the <em>worker-pool</em> model
|
|
|
|
in mind, where multiple threads wait on a single queue and pick up the
|
|
|
|
first goal to execute. Below is a simple implementation where the
|
|
|
|
workers execute arbitrary Prolog goals. Note that this example provides
|
|
|
|
no means to tell when all work is done. This must be realised using
|
|
|
|
additional synchronisation.
|
|
|
|
|
|
|
|
~~~~~
|
|
|
|
% create_workers(+Id, +N)
|
|
|
|
%
|
|
|
|
% Create a pool with given Id and number of workers.
|
|
|
|
|
|
|
|
create_workers(Id, N) :-
|
|
|
|
message_queue_create(Id),
|
|
|
|
forall(between(1, N, _),
|
|
|
|
thread_create(do_work(Id), _, [])).
|
|
|
|
|
|
|
|
do_work(Id) :-
|
|
|
|
repeat,
|
|
|
|
thread_get_message(Id, Goal),
|
|
|
|
( catch(Goal, E, print_message(error, E))
|
|
|
|
-> true
|
|
|
|
; print_message(error, goal_failed(Goal, worker(Id)))
|
|
|
|
),
|
|
|
|
fail.
|
|
|
|
|
|
|
|
% work(+Id, +Goal)
|
|
|
|
%
|
|
|
|
% Post work to be done by the pool
|
|
|
|
|
|
|
|
work(Id, Goal) :-
|
|
|
|
thread_send_message(Id, Goal).
|
|
|
|
~~~~~
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2006-05-25 11:46:23 +01:00
|
|
|
thread_peek_message(Queue, Term) :- var(Queue), !,
|
|
|
|
'$do_error'(instantiation_error,thread_peek_message(Queue,Term)).
|
2006-04-22 12:51:38 +01:00
|
|
|
thread_peek_message(Queue, Term) :-
|
2014-10-13 12:34:52 +01:00
|
|
|
recorded('$thread_alias',[Id|Queue],_R), !,
|
2014-10-14 15:53:24 +01:00
|
|
|
'$message_queue_peek'(Id, Term).
|
2014-10-13 12:34:52 +01:00
|
|
|
tthread_peek_message(Queue, Term) :-
|
|
|
|
'$message_queue_peek'(Queue, Term).
|
2004-02-05 16:57:02 +00:00
|
|
|
|
2014-09-15 19:10:49 +01:00
|
|
|
%% @}
|
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @defgroup Signalling_Threads Signalling Threads
|
|
|
|
@ingroup Threadas
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2004-02-05 16:57:02 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
These predicates provide a mechanism to make another thread execute some
|
|
|
|
goal as an <em>interrupt</em>. Signalling threads is safe as these
|
|
|
|
interrupts are only checked at safe points in the virtual machine.
|
|
|
|
Nevertheless, signalling in multi-threaded environments should be
|
|
|
|
handled with care as the receiving thread may hold a <em>mutex</em>
|
|
|
|
(see with_mutex/2). Signalling probably only makes sense to start
|
|
|
|
debugging threads and to cancel no-longer-needed threads with throw/1,
|
|
|
|
where the receiving thread should be designed carefully do handle
|
|
|
|
exceptions at any point.
|
2004-02-05 16:57:02 +00:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
@{
|
|
|
|
*/
|
2004-02-05 16:57:02 +00:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/** @pred thread_sleep(+ _Time_)
|
|
|
|
|
|
|
|
|
|
|
|
Make current thread sleep for _Time_ seconds. _Time_ may be an
|
|
|
|
integer or a floating point number. When time is zero or a negative value
|
|
|
|
the call succeeds and returns immediately. This call should not be used if
|
|
|
|
alarms are also being used.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
2006-05-25 17:28:28 +01:00
|
|
|
thread_sleep(Time) :-
|
|
|
|
var(Time), !,
|
|
|
|
'$do_error'(instantiation_error,thread_sleep(Time)).
|
|
|
|
thread_sleep(Time) :-
|
2006-12-31 16:10:29 +00:00
|
|
|
integer(Time), !,
|
|
|
|
( Time > 0 ->
|
|
|
|
'$thread_sleep'(Time,0,_,_)
|
|
|
|
; true
|
|
|
|
).
|
2006-05-25 17:28:28 +01:00
|
|
|
thread_sleep(Time) :-
|
2006-12-31 16:10:29 +00:00
|
|
|
float(Time), !,
|
2007-01-01 16:54:39 +00:00
|
|
|
( Time > 0.0 ->
|
2006-12-31 16:10:29 +00:00
|
|
|
STime is integer(float_integer_part(Time)),
|
|
|
|
NTime is integer(float_fractional_part(Time))*1000000000,
|
|
|
|
'$thread_sleep'(STime,NTime,_,_)
|
|
|
|
; true
|
|
|
|
).
|
2006-05-25 17:28:28 +01:00
|
|
|
thread_sleep(Time) :-
|
|
|
|
'$do_error'(type_error(number,Time),thread_sleep(Time)).
|
|
|
|
|
|
|
|
|
2007-01-01 18:11:24 +00:00
|
|
|
thread_signal(Id, Goal) :-
|
|
|
|
'$check_thread_or_alias'(Id, thread_signal(Id, Goal)),
|
2007-01-24 14:20:04 +00:00
|
|
|
'$check_callable'(Goal, thread_signal(Id, Goal)),
|
2007-01-02 03:01:20 +00:00
|
|
|
'$thread_id_alias'(Id0, Id),
|
2007-01-01 18:11:24 +00:00
|
|
|
( recorded('$thread_signal', [Id0| _], R), erase(R), fail
|
|
|
|
; true
|
|
|
|
),
|
|
|
|
recorda('$thread_signal', [Id0| Goal], _),
|
|
|
|
'$signal_thread'(Id0).
|
2004-02-05 16:57:02 +00:00
|
|
|
|
|
|
|
'$thread_gfetch'(G) :-
|
|
|
|
'$thread_self'(Id),
|
2004-02-11 13:59:53 +00:00
|
|
|
recorded('$thread_signal',[Id|G],R),
|
2004-02-05 16:57:02 +00:00
|
|
|
erase(R).
|
2006-05-26 00:46:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
%% @}
|
2006-05-26 00:46:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
/** @defgroup Threads_and_Dynamic_Predicates Threads and Dynamic Predicates
|
|
|
|
@ingroup Threads
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
@{
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
Besides queues threads can share and exchange data using dynamic
|
|
|
|
predicates. The multi-threaded version knows about two types of
|
|
|
|
dynamic predicates. By default, a predicate declared <em>dynamic</em>
|
|
|
|
(see dynamic/1) is shared by all threads. Each thread may
|
|
|
|
assert, retract and run the dynamic predicate. Synchronisation inside
|
|
|
|
Prolog guarantees the consistency of the predicate. Updates are
|
|
|
|
<em>logical</em>: visible clauses are not affected by assert/retract
|
|
|
|
after a query started on the predicate. In many cases primitive from
|
|
|
|
thread synchronisation should be used to ensure application invariants on
|
|
|
|
the predicate are maintained.
|
|
|
|
|
|
|
|
Besides shared predicates, dynamic predicates can be declared with the
|
|
|
|
thread_local/1 directive. Such predicates share their
|
|
|
|
attributes, but the clause-list is different in each thread.
|
2014-09-11 20:06:57 +01:00
|
|
|
|
|
|
|
*/
|
2014-09-15 09:13:50 +01:00
|
|
|
|
|
|
|
/** @pred thread_local( _+Functor/Arity_)
|
2007-01-28 16:00:31 +00:00
|
|
|
|
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
related to the dynamic/1 directive. It tells the system that the
|
|
|
|
predicate may be modified using assert/1, retract/1,
|
|
|
|
etc, during execution of the program. Unlike normal shared dynamic
|
|
|
|
data however each thread has its own clause-list for the predicate.
|
|
|
|
As a thread starts, this clause list is empty. If there are still
|
|
|
|
clauses as the thread terminates these are automatically reclaimed by
|
|
|
|
the system. The `thread_local` property implies
|
|
|
|
the property `dynamic`.
|
2008-08-07 21:51:23 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
Thread-local dynamic predicates are intended for maintaining
|
|
|
|
thread-specific state or intermediate results of a computation.
|
2008-08-07 21:51:23 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
It is not recommended to put clauses for a thread-local predicate into
|
|
|
|
a file as in the example below as the clause is only visible from the
|
|
|
|
thread that loaded the source-file. All other threads start with an
|
|
|
|
empty clause-list.
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
~~~~~
|
|
|
|
:- thread_local
|
|
|
|
foo/1.
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
foo(gnat).
|
|
|
|
~~~~~
|
2014-09-11 20:06:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
*/
|
|
|
|
thread_local(X) :-
|
|
|
|
'$current_module'(M),
|
|
|
|
'$thread_local'(X,M).
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
'$thread_local'(X,M) :- var(X), !,
|
|
|
|
'$do_error'(instantiation_error,thread_local(M:X)).
|
|
|
|
'$thread_local'(Mod:Spec,_) :- !,
|
|
|
|
'$thread_local'(Spec,Mod).
|
|
|
|
'$thread_local'([], _) :- !.
|
|
|
|
'$thread_local'([H|L], M) :- !, '$thread_local'(H, M), '$thread_local'(L, M).
|
|
|
|
'$thread_local'((A,B),M) :- !, '$thread_local'(A,M), '$thread_local'(B,M).
|
|
|
|
'$thread_local'(X,M) :- !,
|
|
|
|
'$thread_local2'(X,M).
|
2014-09-11 20:06:57 +01:00
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
'$thread_local2'(A/N, Mod) :- integer(N), atom(A), !,
|
|
|
|
functor(T,A,N),
|
|
|
|
(Mod \= idb -> '$flags'(T,Mod,F,F) ; true),
|
|
|
|
( '$install_thread_local'(T,Mod) -> true ;
|
|
|
|
F /\ 0x08002000 =\= 0 -> '$do_error'(permission_error(modify,dynamic_procedure,A/N),thread_local(Mod:A/N)) ;
|
|
|
|
'$do_error'(permission_error(modify,static_procedure,A/N),thread_local(Mod:A/N))
|
|
|
|
).
|
|
|
|
'$thread_local2'(X,Mod) :-
|
|
|
|
'$do_error'(type_error(callable,X),thread_local(Mod:X)).
|
2014-09-11 20:06:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
2014-09-15 09:13:50 +01:00
|
|
|
%% @}
|
2014-09-10 05:53:41 +01:00
|
|
|
|
2010-01-14 15:58:19 +00:00
|
|
|
|
2014-09-11 20:06:57 +01:00
|
|
|
/**
|
|
|
|
@}
|
|
|
|
*/
|
2014-09-15 19:10:49 +01:00
|
|
|
>
|