1-- Signals a test raises, the fake of core/io/signal's `signals`, over a
2-- round of turns the test gives it.
3
4import core/fibre
5import core/io
6import core/io/signal
7import core/mem
8
9-- How many signals `signal.kind` names.
10kinds: usize = 6
11
12-- The number the fake gives each signal, in `slot`'s order: Linux's own
13-- on x86-64 and arm64.
14numbers: [6]u32 = [2, 15, 1, 17, 10, 12]
15
16--- Caller-owned state of a fake source of signals: which are watched,
17--- what was raised and not yet read, in the order it was raised, and its
18--- counts. Declare it `zeroed` and give it to `signals_over`, which fills
19--- it; it must outlive the handle. `raised` counts the raises, `ignored`
20--- those of a signal nobody watched, `delivered` the events read, and
21--- `turns` the turns `next_signal` took while it waited.
22public signals_space: type = struct
23 watched: [6]bool
24 waiting: [6]u8
25 queued: usize
26 stopped: bool
27 raised: usize
28 ignored: usize
29 delivered: usize
30 turns: usize
31end signals_space
32
33signals_value: type = struct
34 state: ptr mut signals_space
35 runs: any fibre.turns
36end signals_value
37
38--- A source of signals a test raises, a provider of `signal.signals`
39--- over the round of turns it is given, which it is also a provider of
40--- `fibre.turns` through, as the concept asks: its `start`, `finished`,
41--- `wait` and `yield` are the round's. `raise` delivers a signal as the
42--- system would, and `next_signal` waits for one by taking turns, so a
43--- fibre that waits for a signal lets the test's other fibres run until
44--- one of them raises it. A raised signal nobody watches is counted and
45--- dropped, where a system would act on it. A signal raised again before
46--- it is read is one event, as the systems keep it. The fake numbers
47--- each signal as Linux does on x86-64 and arm64. Copies share the
48--- state.
49public signals: type = signals_value
50
51--- Fill `state` for a source of signals whose waits take turns on
52--- `runs`, watching nothing, and return its handle.
53public signals_over: (inout state: signals_space, runs: any fibre.turns)
54 -> (host: signals from state, runs) =
55 state = (watched: [false, false, false, false, false, false],
56 waiting: [0, 0, 0, 0, 0, 0], queued: 0, stopped: false,
57 raised: 0, ignored: 0, delivered: 0, turns: 0)
58 host = (state: addr state, runs: runs)
59end signals_over
60
61-- Where a signal sits in `numbers` and `watched`.
62slot: (which: signal.kind) -> (index: usize) =
63 index = match which
64 signal.interrupt: 0
65 signal.terminate: 1
66 signal.hangup: 2
67 signal.child: 3
68 signal.user1: 4
69 signal.user2: 5
70 end match
71end slot
72
73-- The signal at `index`, `slot`'s inverse.
74kind_at: (index: usize) -> (which: signal.kind) =
75 which = if index == 0 then signal.interrupt
76 elsif index == 1 then signal.terminate
77 elsif index == 2 then signal.hangup
78 elsif index == 3 then signal.child
79 elsif index == 4 then signal.user1
80 else signal.user2 end if
81end kind_at
82
83--- Raise `which` as the system would deliver it: a watched signal waits
84--- to be read, after any raised before it, unless it already waits; one
85--- nobody watches is counted in `ignored` and dropped.
86public raise: (host: signals, which: signal.kind) -> none =
87 state: ptr mut signals_space = host.state
88 inc state.val.raised
89 index: usize = slot(which)
90 if not state.val.watched[index] or state.val.stopped then
91 inc state.val.ignored
92 return
93 end if
94 for at in usize(0)..<state.val.queued do
95 return when usize(state.val.waiting[at]) == index
96 end for
97 state.val.waiting[state.val.queued] = u8(index)
98 inc state.val.queued
99end raise
100
101fake_watch: (self: signals, which: signal.kind) -> none ! io.io_failed =
102 state: ptr mut signals_space = self.state
103 state.val.watched[slot(which)] = true
104 state.val.stopped = false
105end fake_watch
106
107fake_next_signal: (self: signals) -> (received: signal.event)
108 ! io.io_failed =
109 state: ptr mut signals_space = self.state
110 while state.val.queued == 0 do
111 fail io.io_failed when state.val.stopped
112 inc state.val.turns
113 self.runs.yield()
114 end while
115 fail io.io_failed when state.val.stopped
116 index: usize = usize(state.val.waiting[0])
117 dec state.val.queued
118 for at in usize(0)..<state.val.queued do
119 state.val.waiting[at] = state.val.waiting[at + 1]
120 end for
121 inc state.val.delivered
122 received = (which: kind_at(index), number: numbers[index])
123end fake_next_signal
124
125fake_stop_watching: (self: signals) -> none ! io.io_failed =
126 state: ptr mut signals_space = self.state
127 for index in usize(0)..<kinds do
128 state.val.watched[index] = false
129 end for
130 state.val.queued = 0
131 state.val.stopped = true
132end fake_stop_watching
133
134fake_start: (self: signals, escaping record: ptr mut fibre.space,
135 escaping stack: []mut u8, escaping work: any fibre.task)
136 -> none ! mem.out_of_memory =
137 try self.runs.start(record, stack, work)
138end fake_start
139
140fake_finished: (self: signals, record: fibre.space) -> (yes: bool) =
141 yes = self.runs.finished(record)
142end fake_finished
143
144fake_wait: (self: signals) -> none =
145 self.runs.wait()
146end fake_wait
147
148fake_yield: (self: signals) -> none =
149 self.runs.yield()
150end fake_yield
151
152signals is fibre.scheduler (start: fake_start, finished: fake_finished,
153 wait: fake_wait)
154signals is fibre.turns (yield: fake_yield)
155signals is signal.signals (watch: fake_watch, next_signal: fake_next_signal,
156 stop_watching: fake_stop_watching)