Timeout option in read_term according to call_with_time_limit/2 ? (How to check for non-blocking input.)

Dear Jan, all,

Manual on call_with_time_limit/2 says:
Blocking I/O can be handled using the timeout option of read_term/3.

Going to read_term/3 and then to read_term/2 but no timeout option is described there.

calling
read_term( X, [timeout(3)] ).
results to at infinity
|:

What I am trying to do is to at certain times,
ask if there was something pressed on the keyboard, then respond to it,
if nothing was pressed, then continue without waiting for an input.

Regards,

Nicos Angelopoulos
http://stoics.org.uk/~nicos

ps. the forum does n’t seem to have a tag relevant to documention

The manual is wrong :frowning: There are two options. One is to use set_stream/2 to set a timeout on the stream and the other is to use wait_for_input/3.

The choice depends mostly on what you want to do if the user is half-way typing a term and pauses for a while. This will cause an exception if the stream timeout option is used. Using wait_for_input/3 you can check whether anything is pending or arrives within a certain time. If yes, you call read_term/3. Both are based in the end on the poll() or select() APIs and thus require listening on a device for which these calls are defined, i.e., a file handle on Unix or a socket on Windows.

For this you should use threads, because you don’t want to wait on input. Something like this:

start :-
   setup_call_cleanup(
      thread_create(check_kbd,_,[alias(kbd_thread)]),
      my_other_goals,
      thread_join(kbd_thread)
   ).

check_kbd :-
   read_key(T),
   kbd_active(T),
   (  T == z   % Exit when we type 'z'
   -> thread_send_message(main,end)
   ;  check_kbd
   ).

kbd_active(T) :-
   format('Someone typed ~w.~n',[T]).

my_other_goals :-
   writeln('I woke up just to let you know I am alive'),
   sleep(3),
   end_not_requested,
   my_other_goals.

read_key(K) :-
   get_single_char(C),
   atom_codes(K,[C]).

end_not_requested :-
   \+ thread_peek_message(end). % Fail if we got 'z' on the other thread```

1 ?- start.
I woke up just to let you know I am alive
I woke up just to let you know I am alive
I woke up just to let you know I am alive
Someone typed a.
I woke up just to let you know I am alive
I woke up just to let you know I am alive
I woke up just to let you know I am alive
Someone typed b.
I woke up just to let you know I am alive
Someone typed z.
false.

3 Likes

In the example above I forgot to cleanup the message queue properly, to fix it change end_not_requested as follows:

end_not_requested :-
   (  thread_peek_message(end)  % True if we got 'z' on the other thread
   -> thread_get_message(_),    % clear msg queue for next run
      fail
   ;  true
   ).

Hi,

yes, i noticed !

but many thanks for the code.

Jan’s solution with set_stream/2 is more straightforward, but i walked into a
a weird race condition where get_single_char© will echo the character code
on the terminal (the code is a quick nasty hack). So with that, and wanting to
learn about threads a bit i looked in your code.

one important feature was that i needed the decisions to be taken in the main thread and affect the computation
path. below is some code to illustrate that, and my solution to the clean up issue.

many thanks, that was very helpful,

Nicos Angelopoulos
http://stoics.org.uk/~nicos

start :-
   thread_create(check_kbd, KbdID ),
   assert( check_kbd_id(KbdID) ),
   my_other_goals.

check_kbd :-
   read_key(T),
   check_kbd( T ).

check_kbd( z ) :-
   !,
   thread_send_message(main,end).
check_kbd( T ) :-
   thread_send_message(main,mess(T)),  % main thread gets key press info
   check_kbd.

kbd_active(T) :-
   format('Someone typed ~w.~n',[T]).

my_other_goals :-
   writeln('I woke up just to let you know I am alive'),
   sleep(3),
   end_not_requested.

read_key(K) :-
   get_single_char(C),
   atom_codes(K,[C]).

end_not_requested :-
   thread_peek_message(main, mess(_M)),
   !, % Fail if we got 'z' on the other thread
   thread_get_message(main, Mess),
   process_messages( Mess ).
end_not_requested :-
   thread_peek_message(end),
   !,
   kill_threads.
end_not_requested :-
   my_other_goals.

process_messages( mess(M) ) :-
   kbd_active(M),
   !,
    end_not_requested.
process_messages( end ) :-
   kill_threads.

kill_threads :-
    check_kbd_id( Id ),
    retractall( check_kbd_id(_) ),
    thread_join(Id).
1 Like

Great! I am glad it helped out.
I like the fact that you spread out the code in separate clauses.

Instead of using assert(...) to store the ThreadId you can just use the alias(kbd_thread) option for thread_create/3 and then call thread_join(kbd_thread).

By the way, thanks for all the packs you have contributed! Appreciate them.

Also, notice that kill_threads will fail if for some reason the kbd_thread fails or throws an exception. You can use thread_join(Id,_) to prevent that.

2 Likes

Note there is also thread_get_message/3 that allows for a timeout. Using a timeout of zero it only returns a term if it is pending in the queue.

1 Like