Skip to content
← Back

src/analysis/command_despatcher.ghul

1
namespace Analysis is
2
use System.Exception;
3
use IO.Std;
4
5
use Collections.Iterable;
6
7
use Pair = Collections.KeyValuePair;
8
9
use Ghul.Pipes;
10
11
use IoC;
12
use Logging;
13
use Source;
14
use Compiler;
15
16
class COMMAND_DESPATCHER(
17
_reader: IO.TextReader,
18
_writer: IO.TextWriter,
19
_timers: TIMERS,
20
_log: IO.TextWriter,
21
_watchdog: WATCHDOG,
22
_want_stats_report: bool
23
) is
24
_command_map: Collections.MAP[System.Type,CommandHandler];
25
_last_report_time: System.DateTime;
26
27
_listening: bool;
28
29
init(..) is
30
_last_report_time = System.DateTime.now;
31
_command_map = Collections.MAP[System.Type,CommandHandler]();
32
si
33
34
// The compiler's own version, sourced from the entry assembly's
35
// AssemblyInformationalVersionAttribute (same source as the driver's
36
// startup log line). Exposed in the LISTEN frame so a client can
37
// reject a version older than the minimum it supports; a bad
38
// version-negotiation error is a lot clearer than a mysterious hang.
39
current_compiler_version() -> string? static is
40
let attribute =
41
System.Reflection.Assembly.get_entry_assembly()!
42
.get_custom_attributes(typeof System.Reflection.AssemblyInformationalVersionAttribute, false)
43
|> map(a => cast System.Reflection.AssemblyInformationalVersionAttribute?(a)!)
44
|> first();
45
46
if attribute? then
47
return attribute.informational_version;
48
fi
49
50
return null;
51
si
52
53
// Register a handler keyed by the request variant type. A deserialized
54
// request routes to its handler by runtime type; the variant's
55
// unqualified class name (e.g. `HOVER`, `FORMAT_RANGE`) labels the
56
// per-command timer in the stats report.
57
add_handler(request_variant: System.Type, command_handler: CommandHandler) is
58
assert !_command_map.contains_key(request_variant) else "replacing command handler for {request_variant.name}";
59
60
_command_map[request_variant] = command_handler;
61
si
62
63
poll() -> bool is
64
if !_listening then
65
let listen_capabilities = Collections.LIST[string]();
66
// Opting into the incremental body re-walk via
67
// `--incremental-analysis` is honoured. The flag itself is
68
// still off by default; this capability tells a client it is
69
// safe to pass.
70
listen_capabilities.add("incremental-analysis");
71
72
Protocol.JSON_PROTOCOL.write_response(_writer, Protocol.Response.LISTEN(listen_capabilities, current_compiler_version()));
73
74
_listening = true;
75
fi
76
77
let request: Protocol.Request? mut = null;
78
79
try
80
request = Protocol.JSON_PROTOCOL.read_request(_reader);
81
catch ex: Exception
82
// A frame the analyser couldn't deserialize is a protocol
83
// mismatch, not analyser instability - typically a client
84
// sending a request variant the analyser doesn't know
85
// (older analyser + newer client). Answer with a discrete
86
// error frame so the client can surface it, and keep the
87
// process alive to serve subsequent requests.
88
_log.write_line("ghūl: could not parse request: {ex.get_type()} {ex.message}");
89
90
Protocol.JSON_PROTOCOL.write_response(
91
_writer,
92
Protocol.Response.ERROR("parse", "{ex.get_type().name}: {ex.message}", "")
93
);
94
95
_watchdog.on_operation_complete(_writer);
96
97
return true;
98
yrt
99
100
if !request? then
101
_log.write_line("ghūl: reader is at end");
102
_log.flush();
103
104
return false;
105
fi
106
107
let request_variant = request.get_type();
108
let command_name = request_variant.name;
109
110
if _command_map.contains_key(request_variant) then
111
let handler = _command_map[request_variant];
112
113
_timers.start(command_name);
114
handler.handle(request, _writer);
115
_timers.finish(command_name);
116
117
_watchdog.on_operation_complete(_writer);
118
119
if _want_stats_report then
120
let elapsed_since_last_report = System.DateTime.now.subtract(_last_report_time);
121
122
if elapsed_since_last_report.total_seconds > 60.0D then
123
_last_report_time = System.DateTime.now;
124
125
_log.write(_timers);
126
fi
127
fi
128
129
return true;
130
else
131
// Registered variants with no handler are also a protocol
132
// mismatch - the request kind exists on the wire but this
133
// build doesn't route it anywhere. Same treatment as an
134
// unparseable frame: emit an error and keep going.
135
_log.write_line("ghūl: no handler found for command: '{command_name}'");
136
137
Protocol.JSON_PROTOCOL.write_response(
138
_writer,
139
Protocol.Response.ERROR("unknown_command", "no handler registered for request kind {command_name}", command_name)
140
);
141
142
_watchdog.on_operation_complete(_writer);
143
144
return true;
145
fi
146
si
147
si
148
si