Library
Streams — the system boundary
Everything that crosses the program boundary (timers, sockets, signals, child processes) is a stream with one consumer; this entry defines the shared model.
since 0.1.0-alpha.1linuxwindows
Description
A Leviathan program does not talk to the outside world through special syntax. Timers, sockets,
signals, pipes and files are all library objects that wear the same small stream surface, so
one set of ideas covers all of them. This entry describes that model; std.InStream,
std.OutStream and std.IOStream describe the three view types in detail.
A stream has three parts.
- A buffer,
StreamBuffer<T>: a single-consumer queue. Producerspushvalues onto it, the consumer takes each value out exactly once, in order, andclose()ends it. - A write view,
OutStream<T>. Its<<operator pushes one value and returns the view, so writes chain:out << 1 << 2 << 3pushes three separate items. - A read view,
InStream<T>. It reads the buffer withpull(),pullOrNone(),subscribe(callback), or aforloop.
IOStream<T> is both views over one buffer: whatever is written to it can be read back from it.
Views do not own different queues; any number of views over the same StreamBuffer share it.
A buffer with a write view and a read view
StreamBuffer<int> buf = StreamBuffer();
OutStream<int> out = OutStream(buf);
InStream<int> inp = InStream(buf);
out << 1 << 2 << 3;
console.writeln("buffered: ${buf.count()}");
console.writeln("pull: ${inp.pull()}");
int? second = inp.pullOrNone();
console.writeln("pullOrNone: ${second ?? -1}");
console.writeln("hasData: ${inp.hasData()}");
inp.pullOrNone();
int? none = inp.pullOrNone();
console.writeln("when empty: ${none ?? -1}");
try {
inp.pull();
} catch (RuntimeException e) {
console.writeln("pull on empty: ${e.message}");
}
buffered: 3
pull: 1
pullOrNone: 2
hasData: true
when empty: -1
pull on empty: stream is empty
Pulling from an empty stream with pull() throws; pullOrNone() returns None instead.
Rules
- One consumer. A stream has a single consumer end.
subscribeanditerator()(whichfor ... inuses) are both standing claims on that end, and whichever runs first wins. A laterpull(),subscribeoriterator()throws aRuntimeExceptionthat names the claim:consumer end is claimed by a subscriberorconsumer end is claimed by an iterator. Delivering one stream to several listeners is something a library builds on top of streams, not a property of the stream itself. - Each value is consumed once. Values are removed from the queue as they are read.
- Closing.
close()never throws and may be called any number of times. After a stream is closed,pushsilently drops the value, so a closed consumer receives no further deliveries. Seestd.InStreamfor how closing interacts with values that are still queued. - Streams are disposable.
InStream<T>implementsIDisposable, so a subscription is a resource thatusingreleases on every way out of a scope. Releasing a stream that a producer attached cleanup to (asignal::onstream, for example) runs that cleanup. - Waiting is suspension. A
forloop over a stream that is open and empty waits for the next value without blocking the rest of the program; seelang.awaitandlang.tasks. It ends when the stream is closed and drained. A loop over a stream that is never closed waits forever, so bound it withtake(n)or close the stream from somewhere.
Examples
A subscription to a signal is a stream, and using releases it. When the last subscriber of a
signal is released, the signal returns to its default behavior and the program no longer waits
for it:
Releasing a signal subscription with using
void listen() {
using InStream<int> w = signal::on(signal::USR1);
console.writeln("subscribed");
}
listen();
console.writeln("released");
subscribed
released
Notes
Timer.ticks() (one tick number per firing) and signal::on(sig) (one value per delivery of
the signal) return real InStream<int> values. TcpStream, Process and Pty follow the same
shape with callbacks (onData, onClose) and a << or write for the other direction, and a
File offers FileInStream/FileOutStream views; those are separate classes, not
InStream/OutStream. Console offers the same << operator without being an OutStream.
See also
- InStream — The reading end of a stream: a queue of values of type
Tthat something else produces. - OutStream — The writing end of a stream: you push values of type
Tin, and a reader takes them out. - IOStream — A stream that can be both read and written, with both ends over one queue.
- Timer — A source of ticks on the event loop.
- signal — Operating-system signals delivered to the program as streams.
- IDisposable — The interface of an object that must be cleaned up when its work is finished.