%---------------------------------------------------------------------------% % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 2020, 2022 The Mercury team. % This file is distributed under the terms specified in COPYING.LIB. %---------------------------------------------------------------------------% % % File: thread.closeable_channel.m. % Main author: wangp. % Stability: low. % % Unbounded closeable channels. % %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- module thread.closeable_channel. :- interface. :- import_module bool. :- import_module io. %---------------------------------------------------------------------------% :- type closeable_channel(T). % Initialise a channel. % :- pred init(closeable_channel(T)::out, io::di, io::uo) is det. % Put an item at the end of the channel. % Returns `yes' if successful, or `no' if the channel is closed. % :- pred put(closeable_channel(T)::in, T::in, bool::out, io::di, io::uo) is det. % Close a channel. Once a channel is closed, no more items can be added % to it. Closing a channel that is already closed has no effect. % :- pred close(closeable_channel(T)::in, io::di, io::uo) is det. :- type take_result(T) ---> ok(T) ; closed. % Take an item from the start of the channel, blocking until an item is % available or until the channel is closed. Returns `ok(Item)' if Item % was taken, or `closed' if the channel is closed. % :- pred take(closeable_channel(T)::in, take_result(T)::out, io::di, io::uo) is det. :- type try_take_result(T) ---> ok(T) ; closed ; would_block. % Take an item from the start of the channel, but do not block. % Returns `ok(Item)' if Item was taken from the channel, % `closed' if no item was taken because the channel is closed, or % `would_block' if no item could be taken from the channel without % blocking. `would_block' may be returned even if the channel is not % empty. % :- pred try_take(closeable_channel(T)::in, try_take_result(T)::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module thread.mvar. %---------------------------------------------------------------------------% % Invariants: % - If the channel is open, the hole at the write end is empty. % - If the channel is closed, the hole at the write end is full and % contains `closed'. % :- type closeable_channel(T) ---> channel( mvar(stream(T)), % Read end. mvar(stream(T)) % Write end. ). :- type stream(T) == mvar(item(T)). :- type item(T) ---> item( T, % The current item. stream(T) % The rest of the stream. ) ; closed. % End of the stream. init(channel(Read, Write), !IO) :- mvar.init(Read, !IO), mvar.init(Write, !IO), mvar.init(Hole, !IO), mvar.put(Read, Hole, !IO), mvar.put(Write, Hole, !IO). put(channel(_Read, Write), Val, Success, !IO) :- mvar.init(NewHole, !IO), mvar.take(Write, OldHole, !IO), mvar.try_put(OldHole, item(Val, NewHole), Success, !IO), ( Success = yes, % The channel was open, and remains open. mvar.put(Write, NewHole, !IO) ; Success = no, % The channel was closed, and remains closed. mvar.put(Write, OldHole, !IO) ). close(channel(_Read, Write), !IO) :- mvar.take(Write, Hole, !IO), % We have exclusive WRITE access to the hole. % If the channel is open, the hole is empty so this will succeed. % If the channel is closed, the hole is already full with `closed'. mvar.try_put(Hole, closed, _Success, !IO), mvar.put(Write, Hole, !IO). take(channel(Read, _Write), Res, !IO) :- mvar.take(Read, Head, !IO), mvar.read(Head, ItemOrClosed, !IO), ( ItemOrClosed = item(Val, NewHead), Res = ok(Val) ; ItemOrClosed = closed, Res = closed, NewHead = Head ), mvar.put(Read, NewHead, !IO). try_take(channel(Read, _Write), Res, !IO) :- mvar.take(Read, Head, !IO), mvar.try_read(Head, TryRead, !IO), ( TryRead = yes(ItemOrClosed), ( ItemOrClosed = item(Val, NewHead), Res = ok(Val) ; ItemOrClosed = closed, Res = closed, NewHead = Head ) ; TryRead = no, Res = would_block, NewHead = Head ), mvar.put(Read, NewHead, !IO). %---------------------------------------------------------------------------% :- end_module thread.closeable_channel. %---------------------------------------------------------------------------%