%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% :- module closeable_channel_test. :- interface. :- import_module io. :- pred main(io::di, io::uo) is cc_multi. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module bool. :- import_module int. :- import_module io.environment. :- import_module list. :- import_module maybe. :- import_module string. :- import_module thread. :- import_module thread.closeable_channel. :- import_module thread.semaphore. %---------------------------------------------------------------------------% main(!IO) :- ( if can_spawn then run_test(!IO) else io.write_string("spawn/3 not supported in this grade\n", !IO) ). :- pred run_test(io::di, io::uo) is cc_multi. run_test(!IO) :- % Output from worker threads is disabled by default as it is % non-deterministic. io.environment.get_environment_var("VERBOSE", Verbose, !IO), ( Verbose = yes(_), LogA = log_on("huey"), LogB = log_on("dewey"), LogC = log_on("louie") ; Verbose = no, LogA = log_off, LogB = log_off, LogC = log_off ), closeable_channel.init(InCh, !IO), closeable_channel.init(OutCh, !IO), semaphore.init(DoneA, !IO), semaphore.init(DoneB, !IO), semaphore.init(DoneC, !IO), DoneSems = [DoneA, DoneB, DoneC], Log = log_on("main"), log(Log, "starting worker threads", !IO), thread.spawn(thread_proc(LogA, InCh, OutCh, DoneA), !IO), thread.spawn(thread_proc(LogB, InCh, OutCh, DoneB), !IO), thread.spawn(thread_proc(LogC, InCh, OutCh, DoneC), !IO), % Test try_take from open and empty channel. trace_try_take(Log, OutCh, _, !IO), % Write to open channel. Inputs = 1 .. 9, list.foldl(trace_put(Log, InCh), Inputs, !IO), % Close open channel. trace_close(Log, InCh, !IO), % Wait for threads to exit. log(Log, "waiting for worker threads to exit", !IO), list.foldl(semaphore.wait, DoneSems, !IO), log(Log, "workers exited", !IO), % Write to closed channel. trace_put(Log, InCh, 999, !IO), % Close output channel. trace_close(Log, OutCh, !IO), % Read from closed channel. channel_to_list(OutCh, OutputsA0, !IO), sort(OutputsA0, OutputsA), % Closing a channel again has no effect. trace_close(Log, OutCh, !IO), % Read from empty closed channel does not block. channel_to_list(OutCh, OutputsB, !IO), io.write_string("outputs (a) = ", !IO), io.write(OutputsA, !IO), io.nl(!IO), io.write_string("outputs (b) = ", !IO), io.write(OutputsB, !IO), io.nl(!IO). :- pred thread_proc(log::in, closeable_channel(int)::in, closeable_channel(int)::in, semaphore::in, io::di, io::uo) is cc_multi. thread_proc(Log, InCh, OutCh, DoneSem, !IO) :- log(Log, "thread start", !IO), thread_loop(Log, InCh, OutCh, !IO), log(Log, "thread exit", !IO), semaphore.signal(DoneSem, !IO). :- pred thread_loop(log::in, closeable_channel(int)::in, closeable_channel(int)::in, io::di, io::uo) is cc_multi. thread_loop(Log, InCh, OutCh, !IO) :- trace_take(Log, InCh, TakeResult, !IO), ( TakeResult = ok(Input), Output = Input * Input, trace_put(Log, OutCh, Output, !IO), thread_loop(Log, InCh, OutCh, !IO) ; TakeResult = closed ). :- pred channel_to_list(closeable_channel(int)::in, list(int)::out, io::di, io::uo) is det. channel_to_list(Ch, List, !IO) :- closeable_channel.take(Ch, TakeResult, !IO), ( TakeResult = ok(Head), channel_to_list(Ch, Tail, !IO), List = [Head | Tail] ; TakeResult = closed, List = [] ). %---------------------------------------------------------------------------% :- pred trace_close(log::in, closeable_channel(int)::in, io::di, io::uo) is det. trace_close(Log, Ch, !IO) :- log(Log, "closing channel...", !IO), closeable_channel.close(Ch, !IO), log(Log, "closed channel", !IO). :- pred trace_put(log::in, closeable_channel(int)::in, int::in, io::di, io::uo) is det. trace_put(Log, Ch, Item, !IO) :- logf(Log, "put %d...", [i(Item)], !IO), closeable_channel.put(Ch, Item, Success, !IO), ( Success = yes, logf(Log, "put %d", [i(Item)], !IO) ; Success = no, logf(Log, "put %d - channel closed", [i(Item)], !IO) ). :- pred trace_take(log::in, closeable_channel(int)::in, take_result(int)::out, io::di, io::uo) is det. trace_take(Log, Ch, TakeResult, !IO) :- log(Log, "take...", !IO), closeable_channel.take(Ch, TakeResult, !IO), ( TakeResult = ok(Item), logf(Log, "take - got %d", [i(Item)], !IO) ; TakeResult = closed, log(Log, "take - channel closed", !IO) ). :- pred trace_try_take(log::in, closeable_channel(int)::in, try_take_result(int)::out, io::di, io::uo) is det. trace_try_take(Log, Ch, TakeResult, !IO) :- log(Log, "try_take...", !IO), closeable_channel.try_take(Ch, TakeResult, !IO), ( TakeResult = ok(Item), logf(Log, "try_take - got %d", [i(Item)], !IO) ; TakeResult = closed, log(Log, "try_take - channel closed", !IO) ; TakeResult = would_block, log(Log, "try_take - would block (possibly empty)", !IO) ). %---------------------------------------------------------------------------% :- mutable(log_lock, semaphore, semaphore.impure_init(1), ground, [untrailed, constant]). :- type log ---> log_on(string) ; log_off. :- pred log(log::in, string::in, io::di, io::uo) is det. log(Log, Str, !IO) :- ( Log = log_on(Label), write_log(Label, Str, !IO) ; Log = log_off ). :- pred logf(log::in, string::in, list(poly_type)::in, io::di, io::uo) is det. logf(Log, Format, Args, !IO) :- ( Log = log_on(Label), write_log(Label, string.format(Format, Args), !IO) ; Log = log_off ). :- pred write_log(string::in, string::in, io::di, io::uo) is det. write_log(Label, Str, !IO) :- get_log_lock(Lock), semaphore.wait(Lock, !IO), io.write_string(Label, !IO), io.write_string(": ", !IO), io.write_string(Str, !IO), io.nl(!IO), semaphore.signal(Lock, !IO).