1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! What is known about whether a server answers `textDocument/diagnostic`.
//!
//! `diagnosticProvider` is the advertisement, but not the truth: pyright
//! advertises none and answers pulls anyway, and a push-only server answers
//! `-32601`. A server that advertises nothing is therefore probed once; what
//! the probe showed is kept per process in its slot.
/// What probing a server that advertises no pull provider has shown so far.
///
/// Held in the server's slot and fresh for every replacement process.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(super) enum PullProbe {
/// No pull request has been sent to the process yet.
#[default]
Untried,
/// The process answered a pull request.
Answered,
/// The process answered a pull request with `-32601`, or timed out on two
/// in a row.
Refused,
/// A pull request timed out: a second one that was sent after that
/// timeout, and times out too, refuses the process.
///
/// One timeout is not enough, since a large workspace's first pull can
/// outlast the request timeout on a server that answers pulls.
TimedOutOnce,
}
impl PullProbe {
/// The probe after a pull request that was sent while the probe was
/// `sent_with` timed out.
///
/// Only a request sent after an earlier timeout can be the second one: two
/// requests in flight together that both time out are one failure to
/// answer in time, not two. An answer ends the count, and a transient
/// reply (`-32800`, `-32801`, `-32802`) neither adds to it nor ends it.
pub(super) const fn after_timeout(self, sent_with: Self) -> Self {
match self {
Self::Answered | Self::Refused => self,
Self::Untried | Self::TimedOutOnce => match sent_with {
Self::TimedOutOnce | Self::Refused => Self::Refused,
Self::Untried | Self::Answered => Self::TimedOutOnce,
},
}
}
}
/// Whether `get_diagnostics` can expect a pull answer from a server.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum PullSupport {
/// The server advertises `diagnosticProvider`.
Advertised,
/// Nothing advertised and not tried yet: the next pull is the probe.
Probing,
/// Nothing advertised, but the server answered a pull.
Answers,
/// Nothing advertised, and the server refused a pull.
Unsupported,
}
impl PullSupport {
/// The support of a server that advertises a pull provider or not, with
/// what its probe showed.
pub(super) const fn of(advertised: bool, probe: PullProbe) -> Self {
match (advertised, probe) {
(true, _) => Self::Advertised,
(false, PullProbe::Untried | PullProbe::TimedOutOnce) => Self::Probing,
(false, PullProbe::Answered) => Self::Answers,
(false, PullProbe::Refused) => Self::Unsupported,
}
}
/// Whether `get_diagnostics` sends a pull request.
pub(super) const fn sends_pull(self) -> bool {
!matches!(self, Self::Unsupported)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_advertising_wins_over_any_probe_result() {
for probe in [
PullProbe::Untried,
PullProbe::TimedOutOnce,
PullProbe::Answered,
PullProbe::Refused,
] {
assert_eq!(PullSupport::of(true, probe), PullSupport::Advertised);
}
}
#[test]
fn test_unadvertised_support_follows_the_probe() {
assert_eq!(
PullSupport::of(false, PullProbe::Untried),
PullSupport::Probing
);
assert_eq!(
PullSupport::of(false, PullProbe::Answered),
PullSupport::Answers
);
assert_eq!(
PullSupport::of(false, PullProbe::Refused),
PullSupport::Unsupported
);
}
#[test]
fn test_one_timeout_keeps_probing_and_a_later_sent_second_refuses() {
let once = PullProbe::Untried.after_timeout(PullProbe::Untried);
assert_eq!(once, PullProbe::TimedOutOnce);
assert_eq!(PullSupport::of(false, once), PullSupport::Probing);
assert_eq!(once.after_timeout(once), PullProbe::Refused);
}
#[test]
fn test_overlapping_timeouts_count_once() {
let once = PullProbe::Untried.after_timeout(PullProbe::Untried);
assert_eq!(
once.after_timeout(PullProbe::Untried),
PullProbe::TimedOutOnce
);
}
#[test]
fn test_a_timeout_never_undoes_an_answer_or_a_refusal() {
for sent_with in [PullProbe::Untried, PullProbe::TimedOutOnce] {
assert_eq!(
PullProbe::Answered.after_timeout(sent_with),
PullProbe::Answered
);
assert_eq!(
PullProbe::Refused.after_timeout(sent_with),
PullProbe::Refused
);
}
}
#[test]
fn test_only_a_refusing_server_is_not_pulled() {
for support in [
PullSupport::Advertised,
PullSupport::Probing,
PullSupport::Answers,
] {
assert!(support.sends_pull());
}
assert!(!PullSupport::Unsupported.sends_pull());
}
}