Skip to content
← Back

src/analysis/request_reader.ghul

1
namespace Analysis is
2
use System.Exception
3
use System.Threading.Monitor
4
5
// A frame the reader thread lifted off the client's pipe, or the
6
// reason it could not. Deserialization happens on the reader thread,
7
// so a malformed line becomes a queued PARSE_ERROR rather than an
8
// exception raised at whatever point the despatcher happened to read.
9
//
10
// TIMED_OUT is not read off the pipe at all: it is what a bounded
11
// wait returns when nothing arrived, which is what the idle timeout
12
// is decided from.
13
union QueuedFrame is
14
REQUEST(request: Protocol.Request)
15
PARSE_ERROR(message: string)
16
END_OF_INPUT
17
TIMED_OUT
18
si
19
20
// Reads request frames off the client's pipe on a thread of its own
21
// and queues them, so the despatcher can see what is waiting instead
22
// of only what it has already asked for.
23
//
24
// That is what lets a query be answered during a compile: a handler
25
// running on the main thread can ask whether anything is queued and
26
// take it. Nothing else about the analyser becomes concurrent -
27
// every request is still handled on the main thread, one at a time,
28
// and this class touches no compiler state.
29
class REQUEST_READER(_reader: IO.TextReader) is
30
_gate: object
31
_frames: Collections.LIST[QueuedFrame]
32
33
init(..) is
34
_gate = object()
35
_frames = Collections.LIST[QueuedFrame]()
36
si
37
38
start() is
39
let start: System.Threading.ThreadStart = () is _read_loop(); si
40
41
let thread = System.Threading.Thread(start)
42
43
// A background thread does not hold the process open, which
44
// is what makes the idle exit and the watchdog recycle able
45
// to end the process while this thread sits in read_line.
46
thread.is_background = true
47
thread.name = "ghul-analysis-reader"
48
49
thread.start()
50
si
51
52
_read_loop() is
53
do
54
let line: string? mut = null
55
56
try
57
line = _reader.read_line()
58
catch ex: Exception
59
// The pipe went away under us; that is end of input
60
// by another name.
61
line = null
62
yrt
63
64
if !line? then
65
_push(QueuedFrame.END_OF_INPUT)
66
67
return
68
fi
69
70
if string.is_null_or_white_space(line) then
71
continue
72
fi
73
74
_push(_deserialize(line))
75
od
76
si
77
78
_deserialize(line: string) -> QueuedFrame static is
79
try
80
let request = Protocol.JSON_PROTOCOL.deserialize_request(line)
81
82
if !request? then
83
return QueuedFrame.PARSE_ERROR("request line deserialized to nothing")
84
fi
85
86
return QueuedFrame.REQUEST(request)
87
catch ex: Exception
88
return QueuedFrame.PARSE_ERROR("{ex.get_type().name}: {ex.message}")
89
yrt
90
si
91
92
_push(frame: QueuedFrame) is
93
Monitor.enter(_gate)
94
95
try
96
_frames.add(frame)
97
98
Monitor.pulse_all(_gate)
99
finally
100
Monitor.exit(_gate)
101
yrt
102
si
103
104
// Wait for the next frame, giving up after timeout_ms with
105
// TIMED_OUT; a timeout of zero or less waits indefinitely.
106
//
107
// END_OF_INPUT is answered to every later call as well as to the
108
// one that reached it, so a caller that keeps asking keeps being
109
// told the same thing rather than blocking forever.
110
take(timeout_ms: int) -> QueuedFrame is
111
Monitor.enter(_gate)
112
113
try
114
while _frames.count == 0 do
115
if timeout_ms <= 0 then
116
Monitor.wait(_gate)
117
elif !Monitor.wait(_gate, timeout_ms) then
118
return QueuedFrame.TIMED_OUT
119
fi
120
od
121
122
let head = _frames[0]
123
124
if isa QueuedFrame.END_OF_INPUT(head) then
125
return head
126
fi
127
128
_frames.remove_at(0)
129
130
return head
131
finally
132
Monitor.exit(_gate)
133
yrt
134
si
135
136
// Take the queued request at the head of the queue when there is
137
// one and `wanted` accepts it, and nothing otherwise. Never
138
// waits, and never reorders: a frame the caller does not want
139
// stops the scan, so requests are still handled in the order the
140
// client sent them.
141
take_if(wanted: (Protocol.Request) -> bool) -> Protocol.Request? is
142
Monitor.enter(_gate)
143
144
try
145
if _frames.count == 0 then
146
return null
147
fi
148
149
let head = _frames[0]
150
151
if let queued: QueuedFrame.REQUEST = head /\ wanted(queued.request) then
152
_frames.remove_at(0)
153
154
return queued.request
155
fi
156
157
return null
158
finally
159
Monitor.exit(_gate)
160
yrt
161
si
162
163
// Whether anything `wanted` accepts is queued anywhere, not only at
164
// the head. Never waits and takes nothing: this answers "is there
165
// something behind me that changes what I am doing", which a
166
// long-running handler asks without wanting to handle it yet.
167
has_pending(wanted: (Protocol.Request) -> bool) -> bool is
168
Monitor.enter(_gate)
169
170
try
171
for frame in _frames do
172
if let queued: QueuedFrame.REQUEST = frame /\ wanted(queued.request) then
173
return true
174
fi
175
od
176
177
return false
178
finally
179
Monitor.exit(_gate)
180
yrt
181
si
182
si
183
si