Skip to content

Commit af5b472

Browse files
authored
feat: reduce server memory consumption (#11162)
This PR reduces the memory consumption of the language server (the watchdog process in particular). In Mathlib, it reduces memory consumption by about 1GB. It also fixes two bugs in the call hierarchy: - When an open file had import errors (e.g. from a transitive build failure), the call hierarchy would not display any usages in that file. Now we use the reference information from the .ilean instead. - When a command would not set a parent declaration (e.g. `#check`), the result was filtered from the call hierarchy. Now we display it as `[anonymous]` instead.
1 parent 3282ac6 commit af5b472

File tree

14 files changed

+384
-134
lines changed

14 files changed

+384
-134
lines changed

src/Lean/Data/Lsp/Extra.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ an ILean finalization notification for the worker and the document version desig
6363
Used for test stability in tests that use the .ileans.
6464
-/
6565
structure WaitForILeansParams where
66-
uri : DocumentUri
67-
version : Nat
66+
uri? : Option DocumentUri := none
67+
version? : Option Nat := none
6868
deriving FromJson, ToJson
6969

7070
structure WaitForILeans where

src/Lean/Data/Lsp/Internal.lean

Lines changed: 154 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ prelude
1010
public import Lean.Expr
1111
public import Lean.Data.Lsp.Basic
1212
public import Lean.Data.JsonRpc
13+
public import Lean.Data.DeclarationRange
1314

1415
public section
1516

@@ -98,27 +99,129 @@ instance : ToJson RefIdent where
9899

99100
end RefIdent
100101

101-
/-- Information about the declaration surrounding a reference. -/
102-
structure RefInfo.ParentDecl where
103-
/-- Name of the declaration surrounding a reference. -/
104-
name : String
105-
/-- Range of the declaration surrounding a reference. -/
106-
range : Lsp.Range
107-
/-- Selection range of the declaration surrounding a reference. -/
108-
selectionRange : Lsp.Range
109-
deriving ToJson
102+
/-- Position information for a declaration. Inlined to reduce memory consumption. -/
103+
structure DeclInfo where
104+
/-- Start line of range. -/
105+
rangeStartPosLine : Nat
106+
/-- Start character of range. -/
107+
rangeStartPosCharacter : Nat
108+
/-- End line of range. -/
109+
rangeEndPosLine : Nat
110+
/-- End character of range. -/
111+
rangeEndPosCharacter : Nat
112+
/-- Start line of selection range. -/
113+
selectionRangeStartPosLine : Nat
114+
/-- Start character of selection range. -/
115+
selectionRangeStartPosCharacter : Nat
116+
/-- End line of selection range. -/
117+
selectionRangeEndPosLine : Nat
118+
/-- End character of selection range. -/
119+
selectionRangeEndPosCharacter : Nat
120+
121+
/-- Converts a set of `DeclarationRanges` to a `DeclInfo`. -/
122+
def DeclInfo.ofDeclarationRanges (r : DeclarationRanges) : DeclInfo where
123+
rangeStartPosLine := r.range.pos.line - 1
124+
rangeStartPosCharacter := r.range.charUtf16
125+
rangeEndPosLine := r.range.endPos.line - 1
126+
rangeEndPosCharacter := r.range.endCharUtf16
127+
selectionRangeStartPosLine := r.selectionRange.pos.line - 1
128+
selectionRangeStartPosCharacter := r.selectionRange.charUtf16
129+
selectionRangeEndPosLine := r.selectionRange.endPos.line - 1
130+
selectionRangeEndPosCharacter := r.selectionRange.endCharUtf16
131+
132+
/-- Range of this parent decl. -/
133+
def DeclInfo.range (i : DeclInfo) : Lsp.Range :=
134+
⟨⟨i.rangeStartPosLine, i.rangeStartPosCharacter⟩, ⟨i.rangeEndPosLine, i.rangeEndPosCharacter⟩⟩
135+
136+
/-- Selection range of this parent decl. -/
137+
def DeclInfo.selectionRange (i : DeclInfo) : Lsp.Range :=
138+
⟨⟨i.selectionRangeStartPosLine, i.selectionRangeStartPosCharacter⟩,
139+
⟨i.selectionRangeEndPosLine, i.selectionRangeEndPosCharacter⟩⟩
140+
141+
instance : ToJson DeclInfo where
142+
toJson i :=
143+
Json.arr #[
144+
i.rangeStartPosLine,
145+
i.rangeStartPosCharacter,
146+
i.rangeEndPosLine,
147+
i.rangeEndPosCharacter,
148+
i.selectionRangeStartPosLine,
149+
i.selectionRangeStartPosCharacter,
150+
i.selectionRangeEndPosLine,
151+
i.selectionRangeEndPosCharacter
152+
]
153+
154+
instance : FromJson DeclInfo where
155+
fromJson?
156+
| .arr xs => do
157+
if xs.size != 8 then
158+
throw s!"Expected list of length 8, not length {xs.size}"
159+
return {
160+
rangeStartPosLine := ← fromJson? xs[0]!
161+
rangeStartPosCharacter := ← fromJson? xs[1]!
162+
rangeEndPosLine := ← fromJson? xs[2]!
163+
rangeEndPosCharacter := ← fromJson? xs[3]!
164+
selectionRangeStartPosLine := ← fromJson? xs[4]!
165+
selectionRangeStartPosCharacter := ← fromJson? xs[5]!
166+
selectionRangeEndPosLine := ← fromJson? xs[6]!
167+
selectionRangeEndPosCharacter := ← fromJson? xs[7]!
168+
}
169+
| _ => throw "Expected list"
170+
171+
/-- Declarations of a file with associated position information. -/
172+
@[expose] def Decls := Std.TreeMap String DeclInfo
173+
deriving EmptyCollection, ForIn Id
174+
175+
instance : ToJson Decls where
176+
toJson m := Json.mkObj <| m.toList.map fun (declName, info) => (declName, toJson info)
177+
178+
instance : FromJson Decls where
179+
fromJson? j := do
180+
let node ← j.getObj?
181+
node.foldlM (init := ∅) fun m k v =>
182+
return m.insert k (← fromJson? v)
110183

111184
/--
112185
Denotes the range of a reference, as well as the parent declaration of the reference.
113186
If the reference is itself a declaration, then it contains no parent declaration.
187+
The position information is inlined to reduce memory consumption.
114188
-/
115189
structure RefInfo.Location where
116-
/-- Range of the reference. -/
117-
range : Lsp.Range
118-
/-- Parent declaration of the reference. `none` if the reference is itself a declaration. -/
119-
parentDecl? : Option RefInfo.ParentDecl
190+
mk' ::
191+
/-- Start line of the range of this location. -/
192+
startPosLine : Nat
193+
/-- Start character of the range of this location. -/
194+
startPosCharacter : Nat
195+
/-- End line of the range of this location. -/
196+
endPosLine : Nat
197+
/-- End character of the range of this location. -/
198+
endPosCharacter : Nat
199+
/--
200+
Parent declaration of the reference. Empty string if the reference is itself a declaration.
201+
We do not use `Option` for memory consumption reasons.
202+
-/
203+
parentDecl : String
120204
deriving Inhabited
121205

206+
/-- Creates a `RefInfo.Location`. -/
207+
def RefInfo.Location.mk (range : Lsp.Range) (parentDecl? : Option String) : RefInfo.Location where
208+
startPosLine := range.start.line
209+
startPosCharacter := range.start.character
210+
endPosLine := range.end.line
211+
endPosCharacter := range.end.character
212+
parentDecl := parentDecl?.getD ""
213+
214+
/-- Range of this location. -/
215+
def RefInfo.Location.range (l : RefInfo.Location) : Lsp.Range :=
216+
⟨⟨l.startPosLine, l.startPosCharacter⟩, ⟨l.endPosLine, l.endPosCharacter⟩⟩
217+
218+
/-- Name of the parent declaration of this location. -/
219+
def RefInfo.Location.parentDecl? (l : RefInfo.Location) : Option String :=
220+
if l.parentDecl.isEmpty then
221+
none
222+
else
223+
some l.parentDecl
224+
122225
/-- Definition site and usage sites of a reference. Obtained from `Lean.Server.RefInfo`. -/
123226
structure RefInfo where
124227
/-- Definition site of the reference. May be `none` when we cannot find a definition site. -/
@@ -128,16 +231,9 @@ structure RefInfo where
128231

129232
instance : ToJson RefInfo where
130233
toJson i :=
131-
let rangeToList (r : Lsp.Range) : List Nat :=
132-
[r.start.line, r.start.character, r.end.line, r.end.character]
133-
let parentDeclToList (d : RefInfo.ParentDecl) : List Json :=
134-
let name := d.name |> toJson
135-
let range := rangeToList d.range |>.map toJson
136-
let selectionRange := rangeToList d.selectionRange |>.map toJson
137-
[name] ++ range ++ selectionRange
138234
let locationToList (l : RefInfo.Location) : List Json :=
139-
let range := rangeToList l.range |>.map toJson
140-
let parentDecl := l.parentDecl?.map parentDeclToList |>.getD []
235+
let range := [l.startPosLine, l.startPosCharacter, l.endPosLine, l.endPosCharacter].map toJson
236+
let parentDecl := l.parentDecl?.map ([toJson ·]) |>.getD []
141237
range ++ parentDecl
142238
Json.mkObj [
143239
("definition", toJson $ i.definition?.map locationToList),
@@ -147,35 +243,30 @@ instance : ToJson RefInfo where
147243
instance : FromJson RefInfo where
148244
-- This implementation is optimized to prevent redundant intermediate allocations.
149245
fromJson? j := do
150-
let toRange (a : Array Json) (i : Nat) : Except String Lsp.Range :=
151-
if h : a.size < i + 4 then
152-
throw s!"Expected list of length 4, not {a.size}"
246+
let toLocation (a : Array Json) : Except String RefInfo.Location := do
247+
if h : a.size 4 ∧ a.size ≠ 5 then
248+
.error s!"Expected list of length 4 or 5, not {a.size}"
153249
else
154-
return {
155-
start := {
156-
line := ← fromJson? a[i]
157-
character := ← fromJson? a[i+1]
250+
let startPosLine ← fromJson? a[0]
251+
let startPosCharacter ← fromJson? a[1]
252+
let endPosLine ← fromJson? a[2]
253+
let endPosCharacter ← fromJson? a[3]
254+
if h' : a.size = 5 then
255+
return {
256+
startPosLine
257+
startPosCharacter
258+
endPosLine
259+
endPosCharacter
260+
parentDecl := ← fromJson? a[4]
158261
}
159-
«end» := {
160-
line := ← fromJson? a[i+2]
161-
character := ← fromJson? a[i+3]
262+
else
263+
return {
264+
startPosLine
265+
startPosCharacter
266+
endPosLine
267+
endPosCharacter
268+
parentDecl := ""
162269
}
163-
}
164-
let toParentDecl (a : Array Json) (i : Nat) : Except String RefInfo.ParentDecl := do
165-
let name ← fromJson? a[i]!
166-
let range ← toRange a (i + 1)
167-
let selectionRange ← toRange a (i + 5)
168-
return ⟨name, range, selectionRange⟩
169-
let toLocation (a : Array Json) : Except String RefInfo.Location := do
170-
if a.size != 4 && a.size != 13 then
171-
.error "Expected list of length 4 or 13, not {l.size}"
172-
let range ← toRange a 0
173-
if a.size == 13 then
174-
let parentDecl ← toParentDecl a 4
175-
return ⟨range, parentDecl⟩
176-
else
177-
return ⟨range, none⟩
178-
179270
let definition? ← j.getObjValAs? (Option $ Array Json) "definition"
180271
let definition? ← match definition? with
181272
| none => pure none
@@ -213,15 +304,28 @@ structure LeanILeanHeaderInfoParams where
213304
directImports : Array ImportInfo
214305
deriving FromJson, ToJson
215306

307+
/--
308+
Used in the `$/lean/ileanHeaderSetupInfo` watchdog <- worker notifications.
309+
Contains status information about `lake setup-file`.
310+
-/
311+
structure LeanILeanHeaderSetupInfoParams where
312+
/-- Version of the file these imports are from. -/
313+
version : Nat
314+
/-- Whether setting up the header using `lake setup-file` has failed. -/
315+
isSetupFailure : Bool
316+
deriving FromJson, ToJson
317+
216318
/--
217319
Used in the `$/lean/ileanInfoUpdate` and `$/lean/ileanInfoFinal` watchdog <- worker notifications.
218320
Contains the definitions and references of the file managed by a worker.
219321
-/
220322
structure LeanIleanInfoParams where
221323
/-- Version of the file these references are from. -/
222-
version : Nat
324+
version : Nat
223325
/-- All references for the file. -/
224-
references : ModuleRefs
326+
references : ModuleRefs
327+
/-- All decls for the file. -/
328+
decls : Decls
225329
deriving FromJson, ToJson
226330

227331
/--

src/Lean/Data/Lsp/Ipc.lean

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@ partial def waitForILeans (waitForILeansId : RequestID := 0) (target : DocumentU
141141
| _ =>
142142
pure ()
143143

144+
partial def waitForWatchdogILeans (waitForILeansId : RequestID := 0) : IpcM Unit := do
145+
writeRequest ⟨waitForILeansId, "$/lean/waitForILeans", WaitForILeansParams.mk none none⟩
146+
while true do
147+
match (← readMessage) with
148+
| .response id _ =>
149+
if id == waitForILeansId then
150+
return
151+
| .responseError id _ msg _ =>
152+
if id == waitForILeansId then
153+
throw $ userError s!"Waiting for ILeans failed: {msg}"
154+
| _ =>
155+
pure ()
156+
144157
/--
145158
Waits for a diagnostic notification with a specific message to be emitted. Discards all received
146159
messages, so should not be combined with `collectDiagnostics`.

src/Lean/Elab/Frontend.lean

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,12 @@ def runFrontend
199199
if let some ileanFileName := ileanFileName? then
200200
let trees := snaps.getAll.flatMap (match ·.infoTree? with | some t => #[t] | _ => #[])
201201
let references := Lean.Server.findModuleRefs inputCtx.fileMap trees (localVars := false)
202+
let (moduleRefs, decls) ← references.toLspModuleRefs
202203
let ilean := {
203204
module := mainModuleName
204205
directImports := Server.collectImports ⟨snap.stx⟩
205-
references := ← references.toLspModuleRefs
206+
references := moduleRefs
207+
decls
206208
: Lean.Server.Ilean
207209
}
208210
IO.FS.writeFile ileanFileName $ Json.compress $ toJson ilean

src/Lean/Server/FileWorker.lean

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,18 @@ section Elab
151151
-- Placed here instead of Lean.Server.Utils because of an import loop
152152
private def mkIleanInfoNotification (method : String) (m : DocumentMeta)
153153
(trees : Array Elab.InfoTree) : BaseIO (JsonRpc.Notification Lsp.LeanIleanInfoParams) := do
154-
let references ← findModuleRefs m.text trees (localVars := true) |>.toLspModuleRefs
155-
let param := { version := m.version, references }
154+
let (references, decls) ← findModuleRefs m.text trees (localVars := true) |>.toLspModuleRefs
155+
let param := { version := m.version, references, decls }
156156
return { method, param }
157157

158-
private def mkInitialIleanInfoUpdateNotification (m : DocumentMeta)
158+
private def mkIleanHeaderInfoNotification (m : DocumentMeta)
159159
(directImports : Array ImportInfo) : JsonRpc.Notification Lsp.LeanILeanHeaderInfoParams :=
160160
{ method := "$/lean/ileanHeaderInfo", param := { version := m.version, directImports } }
161161

162+
private def mkIleanHeaderSetupInfoNotification (m : DocumentMeta)
163+
(isSetupFailure : Bool) : JsonRpc.Notification Lsp.LeanILeanHeaderSetupInfoParams :=
164+
{ method := "$/lean/ileanHeaderSetupInfo", param := { version := m.version, isSetupFailure } }
165+
162166
private def mkIleanInfoUpdateNotification : DocumentMeta → Array Elab.InfoTree →
163167
BaseIO (JsonRpc.Notification Lsp.LeanIleanInfoParams) :=
164168
mkIleanInfoNotification "$/lean/ileanInfoUpdate"
@@ -396,7 +400,7 @@ def setupImports
396400
return .error { diagnostics := .empty, result? := none, metaSnap := default }
397401

398402
let header := stx.toModuleHeader
399-
chanOut.sync.send <| .ofMsg <| mkInitialIleanInfoUpdateNotification doc <| collectImports stx
403+
chanOut.sync.send <| .ofMsg <| mkIleanHeaderInfoNotification doc <| collectImports stx
400404
let fileSetupResult ← setupFile doc header fun stderrLine => do
401405
let progressDiagnostic := {
402406
range := ⟨⟨0, 0⟩, ⟨1, 0⟩⟩
@@ -406,6 +410,9 @@ def setupImports
406410
message := stderrLine
407411
}
408412
chanOut.sync.send <| .ofMsg <| mkPublishDiagnosticsNotification doc #[progressDiagnostic]
413+
let isSetupError := fileSetupResult.kind matches .importsOutOfDate
414+
|| fileSetupResult.kind matches .error ..
415+
chanOut.sync.send <| .ofMsg <| mkIleanHeaderSetupInfoNotification doc isSetupError
409416
-- clear progress notifications in the end
410417
chanOut.sync.send <| .ofMsg <| mkPublishDiagnosticsNotification doc #[]
411418
match fileSetupResult.kind with

src/Lean/Server/FileWorker/RequestHandling.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,12 @@ partial def handleDocumentHighlight (p : DocumentHighlightParams)
266266

267267
let highlightRefs? (snaps : Array Snapshot) : IO (Option (Array DocumentHighlight)) := do
268268
let trees := snaps.map (·.infoTree)
269-
let refs : Lsp.ModuleRefs ← findModuleRefs text trees |>.toLspModuleRefs
269+
let (refs, _) ← findModuleRefs text trees |>.toLspModuleRefs
270270
let mut ranges := #[]
271271
for ident in refs.findAt p.position (includeStop := true) do
272272
if let some info := refs.get? ident then
273-
if let some ⟨definitionRange, _⟩ := info.definition? then
274-
ranges := ranges.push definitionRange
273+
if let some loc := info.definition? then
274+
ranges := ranges.push loc.range
275275
ranges := ranges.append <| info.usages.map (·.range)
276276
if ranges.isEmpty then
277277
return none

0 commit comments

Comments
 (0)