Landin library reference discuss source

hosted/blocking/blocking.ldn

1--  The hosted provider whose waiting operations block the thread (D476),
2--  `host`: reading and writing, running programs, their pipes and their
3--  ends in programs.ldn (D479), core/net's network in network.ldn,
4--  core/time's monotonic clock and its sleep in time.ldn, and core/fibre's
5--  scheduler in fibre.ldn, running each task to its end.  It is built over
6--  a hosted/system handle and hands it every service that does not wait,
7--  in delegated.ldn, so one handle is an `io.world` and every concept the
8--  system supplies.  Written once over platform/posix, like the system.
9--  Import this module only at a hosted authority root.
10
11import core/io
12import core/text
13import hosted/system
14import platform/posix
15
16blocking_value: type = struct
17    services: system.host
18end blocking_value
19
20--- Hosted implementation of all four I/O capabilities and so of
21--- `io.world`, of `io.programs`, `net.network`, the monotonic `time.clock`
22--- and `fibre.scheduler`, whose waits block the thread, and of every
23--- concept `hosted/system` supplies, which it hands the system's handle it
24--- is built over: `io.directories`, `io.environment`, `io.file_system`,
25--- `io.terminals`, `io.entropy` and `time.wall_clock`.  A child is started
26--- by that handle's `start`, which does not wait (D479).
27--- A handle: copies share the process's descriptors, arguments and
28--- environment, and the last-error and last-signal cells they were made
29--- with. Its files are the system's, as `hosted/system`'s `descriptor_of`
30--- and `file_of` give them; borrowed arguments and variables refer to
31--- process storage.
32public host: type = blocking_value
33
34--- The system's last-error and last-signal cells of the caller's own,
35--- `hosted/system`'s `space`, for `over`.
36public space: type = system.space
37
38--- Return errno captured for the terminal failing provider call, as
39--- `hosted/system`'s `last_errno` does: the cell is the system handle's,
40--- which this provider's own calls report through too. Zero means the
41--- handle's most recent call of any concept did not fail with an errno.
42public last_errno: (handle: host) -> (number: i32) =
43    number = system.last_errno(handle.services)
44end last_errno
45
46--- Return the number of the signal that ended the child this handle's
47--- cell last saw end through `io.wait`, as `hosted/system`'s
48--- `last_signal` does; zero when it exited with a code or none was waited
49--- for.
50public last_signal: (handle: host) -> (number: i32) =
51    number = system.last_signal(handle.services)
52end last_signal
53
54--  Keep a failure's errno in the system handle's cell.
55record: (self: host, problem: posix.errno) -> none =
56    system.record_errno(self.services, problem)
57end record
58
59--  Clear the cell before a call asks the system.
60cleared: (self: host) -> none =
61    system.clear_errno(self.services)
62end cleared
63
64--  Write some of `bytes` to `opened`: a send on a socket, a write
65--  otherwise.
66put_some: (opened: io.file, bytes: []u8) -> (count: usize) ! posix.errno =
67    if system.is_socket(opened) then
68        count = try posix.send(system.descriptor_of(opened), bytes)
69    else
70        count = try posix.write(system.descriptor_of(opened), bytes)
71    end if
72end put_some
73
74blocking_read: (self: host, opened: io.file, into: []mut u8)
75               -> (count: usize) ! io.io_failed =
76    cleared(self)
77    if lenof into == 0 then
78        count = 0
79        return
80    end if
81    loop do
82        --  read returns EINTR only before transferring a byte, so the same
83        --  buffer and extent may be retried.  A positive partial read is
84        --  returned and is never discarded by an unnecessary retry.
85        got: usize = posix.read(system.descriptor_of(opened), into)
86            else (problem)
87            continue when problem == posix.eintr
88            record(self, problem)
89            fail io.io_failed
90        end
91        --  A count beyond the buffer violates the system's contract and
92        --  fabricates no errno.  Zero from a nonempty read is genuine EOF.
93        fail io.io_failed when got > lenof into
94        count = got
95        return
96    end loop
97end blocking_read
98
99write_all: (self: host, opened: io.file, bytes: []u8)
100           -> none ! io.io_failed =
101    mut written: usize = 0
102    while written < lenof bytes do
103        remaining: []u8 = bytes[written..<lenof bytes]
104        --  write reports EINTR only before this attempt transfers data.
105        --  Retry the same remaining suffix; a preceding positive partial
106        --  write has already advanced `written` and is never repeated.
107        accepted: usize =
108            put_some(opened, remaining) else (problem)
109                continue when problem == posix.eintr
110                record(self, problem)
111                fail io.io_failed
112            end
113        --  A nonempty zero-progress success cannot complete write-all, and
114        --  an oversized count violates the system's contract.  Neither
115        --  fabricates errno.
116        fail io.io_failed when accepted == 0 or accepted > lenof remaining
117        written += accepted
118    end while
119end write_all
120
121blocking_write: (self: host, opened: io.file, bytes: []u8)
122                -> none ! io.io_failed =
123    cleared(self)
124    return when lenof bytes == 0
125    try write_all(self, opened, bytes)
126end blocking_write
127
128blocking_write_some: (self: host, opened: io.file, bytes: []u8)
129                     -> (count: usize) ! io.io_failed =
130    cleared(self)
131    if lenof bytes == 0 then
132        count = 0
133        return
134    end if
135    --  Keep a single host request within a representable, bounded extent;
136    --  the caller can send the remaining suffix after this progress.
137    mut requested: usize = lenof bytes
138    if requested > 65536 then requested = 65536 end if
139    loop do
140        --  EINTR transfers nothing on this attempt. A positive short write
141        --  returns immediately so its exact prefix reaches the caller.
142        accepted: usize = put_some(opened, bytes[0..<requested])
143            else (problem)
144            continue when problem == posix.eintr
145            record(self, problem)
146            fail io.io_failed
147        end
148        fail io.io_failed when accepted == 0 or accepted > requested
149        count = accepted
150        return
151    end loop
152end blocking_write_some
153
154--  What does not wait is the system's: its files, streams and arguments.
155blocking_open_read: (self: host, path: text.host_string)
156                    -> (opened: io.file)
157                    ! io.not_found | io.no_access | io.io_failed =
158    opened = try io.open_read(self.services, path)
159end blocking_open_read
160
161blocking_open_write: (self: host, path: text.host_string)
162                     -> (opened: io.file)
163                     ! io.not_found | io.no_access | io.io_failed =
164    opened = try io.open_write(self.services, path)
165end blocking_open_write
166
167blocking_same_file: (self: host, left: text.host_string,
168                     right: text.host_string)
169                    -> (same: bool) ! io.io_failed =
170    same = try io.same_file(self.services, left, right)
171end blocking_same_file
172
173blocking_close: (self: host, sink opened: io.file) -> none ! io.io_failed =
174    try io.close(self.services, opened)
175end blocking_close
176
177blocking_input: (self: host) -> (stream: io.file) =
178    stream = io.input(self.services)
179end blocking_input
180
181blocking_out: (self: host) -> (stream: io.file) =
182    stream = io.out(self.services)
183end blocking_out
184
185blocking_err: (self: host) -> (stream: io.file) =
186    stream = io.err(self.services)
187end blocking_err
188
189blocking_argument_count: (self: host) -> (count: usize) =
190    count = io.argument_count(self.services)
191end blocking_argument_count
192
193blocking_argument: (self: host, index: usize)
194                   -> (value: text.host_string from self) ! io.no_argument =
195    value = try io.argument_at(self.services, index)
196end blocking_argument
197
198host is io.reader (read: blocking_read)
199host is io.writer (write: blocking_write, write_some: blocking_write_some)
200host is io.files
201    (open_read: blocking_open_read, open_write: blocking_open_write,
202     same_file: blocking_same_file, close: blocking_close)
203host is io.process
204    (input: blocking_input, out: blocking_out, err: blocking_err,
205     argument_count: blocking_argument_count, argument: blocking_argument)
206host is io.world ()
207
208--- Return a handle to the hosted provider whose waits block the thread,
209--- over a new `hosted/system` handle: the process's arguments, the three
210--- standard streams, descriptors 0, 1 and 2, and the process's last-error
211--- cell. Regular open-write operations create or truncate files; close
212--- regular files explicitly. The handle borrows nothing. Use `over` for a
213--- cell of the caller's own.
214---
215--- Calling this before the hosted entry has established the process's
216--- arguments traps, as `hosted/system`'s `new` does.
217public new: () -> (handle: host) =
218    handle = (services: system.new())
219end new
220
221--- Fill `state` and return a handle that records its last error there
222--- rather than in the process's cell, over `hosted/system`'s `over`. It
223--- shares the process's arguments and descriptors like any other handle;
224--- calling this before any root exists traps, as `new` does.
225public over: (inout state: space) -> (handle: host from state) =
226    handle = (services: system.over(state))
227end over