summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lsp/lsp_client.zig38
1 files changed, 35 insertions, 3 deletions
diff --git a/src/lsp/lsp_client.zig b/src/lsp/lsp_client.zig
index 91899a15..a3f3a954 100644
--- a/src/lsp/lsp_client.zig
+++ b/src/lsp/lsp_client.zig
@@ -195,7 +195,7 @@ const wedged_strikes = 3; // consecutive timeouts before a restart
const max_rows = 2000;
const max_doc_bytes = 8 << 20;
-const Err = error{ Dead, Timeout, Protocol, OutOfMemory, NoServer, WriteFailed };
+const Err = error{ Dead, Timeout, Protocol, OutOfMemory, NoServer, Backoff, WriteFailed };
/// Long-lived state outlives every query arena and cannot borrow the caller's
/// gpa (a different one shows up in the harness than in the shell), so
@@ -438,6 +438,15 @@ pub fn query(gpa: std.mem.Allocator, arena: std.mem.Allocator, asked: lsp.Req, o
if (req.kind == .explain) return traceOut(&tr, req, out, rows, us);
// No server to start for the language: said, not "did not answer".
if (failure) |err| if (err == error.NoServer) return lsp.none(out, "language server for {s} not found", .{std.fs.path.extension(req.path)});
+ // Installed, but it failed a moment ago: said so, and when it is tried
+ // again, not that it is not there.
+ if (failure) |err| if (err == error.Backoff) {
+ const c = &conns[si];
+ c.lock();
+ const left_ms = c.retry_after_ms -| nowMs();
+ c.unlock();
+ return lsp.none(out, "{s} failed recently; retry in {d}s", .{ specs[si].name, @divFloor(left_ms + 999, 1000) });
+ };
if (failure) |err| return err;
try out.writeAll(scratch.buffered());
}
@@ -481,7 +490,7 @@ fn answer(arena: std.mem.Allocator, si: usize, req: lsp.Req, out: *std.Io.Writer
}
}
},
- error.OutOfMemory, error.NoServer, error.WriteFailed => {},
+ error.OutOfMemory, error.NoServer, error.Backoff, error.WriteFailed => {},
}
return e;
};
@@ -1166,7 +1175,7 @@ fn ensure(c: *Conn, si: usize, arena: std.mem.Allocator, req: lsp.Req, tr: *Trac
// already said when the next try is due.
if (nowMs() < c.retry_after_ms) {
tr.note("STOP: {s} failed recently; retry due in {d}ms", .{ specs[si].name, c.retry_after_ms - nowMs() });
- return error.NoServer;
+ return error.Backoff;
}
// Look before forking; a missing binary must cost one probe and one
@@ -2327,3 +2336,26 @@ test "Lspinfo before any query says a server is not started yet, not off" {
try status(.{ .kind = .status, .path = "", .source = "", .offset = 0 }, &out.writer);
try std.testing.expect(std.mem.indexOf(u8, out.written(), "not started yet (its first query starts it)") != null);
}
+
+test "a server that failed a moment ago is said to have failed, and when it is tried again, not to be missing" {
+ if (getenv("PARDES_LSP_ZIG") != null) return error.SkipZigTest;
+ const si = specFor("/x/a.zig").?;
+ const c = &conns[si];
+ c.lock();
+ const was = .{ c.state, c.retry_after_ms };
+ c.state = .dead;
+ c.retry_after_ms = nowMs() + 4500;
+ c.unlock();
+ defer {
+ c.lock();
+ c.state = was[0];
+ c.retry_after_ms = was[1];
+ c.unlock();
+ }
+ var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
+ defer arena.deinit();
+ var out: std.Io.Writer.Allocating = .init(std.testing.allocator);
+ defer out.deinit();
+ try query(std.testing.allocator, arena.allocator(), .{ .kind = .hover, .path = "/x/a.zig", .source = "", .offset = 0 }, &out.writer);
+ try std.testing.expect(std.mem.indexOf(u8, out.written(), "zls failed recently; retry in 5s") != null);
+}