Landin library reference discuss source

hosted/blocking/programs.ldn

1--  The blocking provider's `io.programs` (D479): a child is started by the
2--  system handle this provider is built over, which does not wait, and
3--  its pipes, files of that handle, are read and written as this
4--  provider's `read` and `write` read and write any other, blocking the
5--  thread, as waiting for the child does, POSIX's `waitpid`.
6
7import core/io
8import core/text
9import hosted/system
10import platform/posix
11
12blocking_spawn: (self: host, path: text.host_string,
13                 arguments: []text.host_string, environment: []io.variable,
14                 streams: io.child_streams)
15                -> (started: io.child)
16                ! io.not_found | io.no_access | io.io_failed =
17    started = try system.start(self.services, path, arguments, environment,
18                               streams)
19end blocking_spawn
20
21--  Close one of a child's pipes, which is closed even when the system
22--  reports a failure.
23close_pipe: (self: host, opened: io.file) -> none ! io.io_failed =
24    posix.close(system.descriptor_of(opened)) else (problem)
25        record(self, problem)
26        fail io.io_failed
27    end
28end close_pipe
29
30--  Write all of BYTES to the child's input pipe, blocking the thread while
31--  the pipe is full, as any other write does.
32blocking_write_child: (self: host, inout started: io.child, bytes: []u8)
33                      -> none ! io.io_failed =
34    cleared(self)
35    fail io.io_failed when not started.input_open
36    try write_all(self, started.input, bytes)
37end blocking_write_child
38
39blocking_close_input: (self: host, inout started: io.child)
40                      -> none ! io.io_failed =
41    cleared(self)
42    fail io.io_failed when not started.input_open
43    started.input_open = false
44    try close_pipe(self, started.input)
45end blocking_close_input
46
47--  Whether the child's errors, and not its output, is what to read next:
48--  the output when it has anything, its end included, and the errors only
49--  when they alone do.  A wait a signal interrupted is made again.
50errors_first: (self: host, started: io.child) -> (yes: bool)
51              ! io.io_failed =
52    mut entries: [2]posix.poll_entry =
53        [posix.watch(system.descriptor_of(started.output), posix.poll_input),
54         posix.watch(system.descriptor_of(started.errors), posix.poll_input)]
55    loop do
56        _ = posix.poll(entries[0..<2], -1) else (problem)
57            continue when problem == posix.eintr
58            record(self, problem)
59            fail io.io_failed
60        end
61        break
62    end loop
63    yes = posix.happened(entries[0]) == posix.poll_no_events
64end errors_first
65
66blocking_read_child: (self: host, inout started: io.child, into: []mut u8)
67                     -> (taken: io.child_read) ! io.io_failed =
68    cleared(self)
69    taken = (count: 0, stream: io.child_output,
70             finished: not started.output_open and not started.errors_open)
71    return when lenof into == 0
72    loop do
73        if not started.output_open and not started.errors_open then
74            taken = (count: 0, stream: io.child_output, finished: true)
75            return
76        end if
77        mut from_errors: bool = not started.output_open
78        if started.output_open and started.errors_open then
79            from_errors = try errors_first(self, started)
80        end if
81        opened: io.file = if from_errors then started.errors
82                          else started.output end if
83        count: usize = try blocking_read(self, opened, into)
84        if count > 0 then
85            taken = (count: count,
86                     stream: if from_errors then io.child_errors
87                             else io.child_output end if,
88                     finished: false)
89            return
90        end if
91        --  That stream has ended, and its pipe is done with.
92        if from_errors then
93            started.errors_open = false
94        else
95            started.output_open = false
96        end if
97        try close_pipe(self, opened)
98    end loop
99end blocking_read_child
100
101--  Close every pipe the child still has, keeping the first failure.
102close_pipes: (self: host, started: io.child) -> (closed: bool) =
103    closed = true
104    if started.input_open then
105        close_pipe(self, started.input) else (problem)
106            _ = problem
107            closed = false
108        end
109    end if
110    if started.output_open then
111        close_pipe(self, started.output) else (problem)
112            _ = problem
113            closed = false
114        end
115    end if
116    if started.errors_open then
117        close_pipe(self, started.errors) else (problem)
118            _ = problem
119            closed = false
120        end
121    end if
122end close_pipes
123
124--  Wait for the child, again when a signal interrupts the wait.
125waited: (self: host, child: posix.process_id)
126        -> (status: posix.child_status) ! io.io_failed =
127    loop do
128        status = posix.wait(child) else (problem)
129            continue when problem == posix.eintr
130            record(self, problem)
131            fail io.io_failed
132        end
133        return
134    end loop
135end waited
136
137--  The pipes are closed before the wait, so a child still writing to one
138--  meets a closed pipe rather than waiting for a reader that never comes.
139blocking_wait: (self: host, sink started: io.child)
140               -> (ended: io.ending) ! io.io_failed =
141    cleared(self)
142    closed: bool = close_pipes(self, started)
143    status: posix.child_status =
144        try waited(self, system.process_of(started))
145    ended = system.ended(self.services, status)
146    fail io.io_failed when not closed
147end blocking_wait
148
149host is io.programs
150    (spawn: blocking_spawn, write_child: blocking_write_child,
151     close_input: blocking_close_input, read_child: blocking_read_child,
152     wait: blocking_wait)