@@ -19,12 +19,19 @@ first connect to the session using `$/lean/rpc/connect`. -/
19
19
20
20
namespace Lean.Lsp
21
21
22
- /-- An object which RPC clients can refer to without marshalling. -/
22
+ /--
23
+ An object which RPC clients can refer to without marshalling.
24
+
25
+ The language server may serve the same `RpcRef` multiple times and maintains a reference count
26
+ to track how often it has served the reference.
27
+ If clients want to release the object associated with an `RpcRef`,
28
+ they must release the reference as often as they have received it from the server.
29
+ -/
23
30
structure RpcRef where
24
31
/- NOTE(WN): It is important for this to be a single-field structure
25
32
in order to deserialize as an `Object` on the JS side. -/
26
33
p : USize
27
- deriving BEq, Hashable, FromJson, ToJson
34
+ deriving Inhabited, BEq, Hashable, FromJson, ToJson
28
35
29
36
instance : ToString RpcRef where
30
37
toString r := toString r.p
@@ -33,36 +40,123 @@ end Lean.Lsp
33
40
34
41
namespace Lean.Server
35
42
36
- structure RpcObjectStore : Type where
37
- /-- Objects that are being kept alive for the RPC client, together with their type names,
38
- mapped to by their RPC reference.
43
+ /--
44
+ Marks values to be encoded as opaque references in RPC packets.
45
+ Two identical `WithRpcRef`s with `id? = some id` and the same `id` will yield the same `RpcRef`.
46
+
47
+ See also the docstring for `RpcEncodable`.
48
+ -/
49
+ structure WithRpcRef (α : Type u) where
50
+ private mk' ::
51
+ val : α
52
+ id? : Option USize
53
+ deriving Inhabited
54
+
55
+ /--
56
+ Creates a non-reusable `WithRpcRef`.
57
+ Every time `val` is served to the client, it generates a new `RpcRef`.
58
+ -/
59
+ def WithRpcRef.mkNonReusable (val : α) : WithRpcRef α where
60
+ val
61
+ id? := none
39
62
40
- Note that we may currently have multiple references to the same object. It is only disposed
41
- of once all of those are gone. This simplifies the client a bit as it can drop every reference
42
- received separately. -/
43
- aliveRefs : PersistentHashMap Lsp.RpcRef Dynamic := {}
44
- /-- Value to use for the next `RpcRef`. It is monotonically increasing to avoid any possible
45
- bugs resulting from its reuse. -/
46
- nextRef : USize := 0
63
+ /--
64
+ Alias for `WithRpcRef.mkNonReusable`.
65
+ -/
66
+ def WithRpcRef.mk (val : α) : WithRpcRef α := .mkNonReusable val
47
67
48
- def rpcStoreRef (any : Dynamic) : StateM RpcObjectStore Lsp.RpcRef := do
49
- let st ← get
50
- set { st with
51
- aliveRefs := st.aliveRefs.insert ⟨st.nextRef⟩ any
52
- nextRef := st.nextRef + 1
68
+ builtin_initialize freshWithRpcRefId : IO.Ref USize ← IO.mkRef 0
69
+
70
+ /--
71
+ Creates an `WithRpcRef` instance with a unique `id?`.
72
+ Two identical `WithRpcRef`s with `id? = some id` and the same `id` will yield the same `RpcRef`.
73
+ Hence, storing a `WithRpcRef` produced by `mkReusable` and serving it to the client twice will also
74
+ yield the same `RpcRef` twice, allowing clients to reuse their associated UI state across
75
+ RPC requests.
76
+ -/
77
+ def WithRpcRef.mkReusable (val : α) : BaseIO (WithRpcRef α) := do
78
+ let id ← freshWithRpcRefId.modifyGet fun id => (id, id + 1 )
79
+ return {
80
+ val,
81
+ id? := some id
53
82
}
54
- return ⟨st.nextRef⟩
55
83
56
- def rpcGetRef (r : Lsp.RpcRef) : ReaderT RpcObjectStore Id (Option Dynamic) :=
57
- return (← read).aliveRefs.find? r
84
+ structure ReferencedObject where
85
+ obj : Dynamic
86
+ id? : Option USize
87
+ rc : Nat
88
+
89
+ structure RpcObjectStore : Type where
90
+ /--
91
+ Objects that are being kept alive for the RPC client, together with their type names,
92
+ mapped to by their RPC reference.
93
+ -/
94
+ aliveRefs : PersistentHashMap Lsp.RpcRef ReferencedObject := {}
95
+ /--
96
+ Unique `RpcRef` for the ID of an object that is being referenced through RPC.
97
+ We store this mapping so that we can reuse `RpcRef`s for the same object.
98
+ Reusing `RpcRef`s is helpful because it enables clients to reuse their UI state.
99
+ -/
100
+ refsById : PersistentHashMap USize Lsp.RpcRef := {}
101
+ /--
102
+ Value to use for the next fresh `RpcRef`. It is monotonically increasing to avoid any possible
103
+ bugs resulting from its reuse.
104
+ -/
105
+ nextRef : USize := 0
106
+
107
+ def rpcStoreRef [TypeName α] (obj : WithRpcRef α) : StateM RpcObjectStore Lsp.RpcRef := do
108
+ let st ← get
109
+ let reusableRef? : Option Lsp.RpcRef := do st.refsById.find? (← obj.id?)
110
+ match reusableRef? with
111
+ | some ref =>
112
+ -- Reuse `RpcRef` for this `obj` so that clients can reuse their UI state for it.
113
+ -- We maintain a reference count so that we only free `obj` when the client has released
114
+ -- all of its instances of the `RpcRef` for `obj`.
115
+ let some referencedObj := st.aliveRefs.find? ref
116
+ | return panic! "Found object ID in `refsById` but not in `aliveRefs`."
117
+ let referencedObj := { referencedObj with rc := referencedObj.rc + 1 }
118
+ set { st with aliveRefs := st.aliveRefs.insert ref referencedObj }
119
+ return ref
120
+ | none =>
121
+ let ref : Lsp.RpcRef := ⟨st.nextRef⟩
122
+ set { st with
123
+ aliveRefs :=
124
+ st.aliveRefs.insert ref ⟨.mk obj.val, obj.id?, 1 ⟩
125
+ refsById :=
126
+ match obj.id? with
127
+ | none => st.refsById
128
+ | some id => st.refsById.insert id ref
129
+ nextRef :=
130
+ st.nextRef + 1
131
+ }
132
+ return ref
133
+
134
+ def rpcGetRef (α) [TypeName α] (r : Lsp.RpcRef)
135
+ : ReaderT RpcObjectStore (ExceptT String Id) (WithRpcRef α) := do
136
+ let some referencedObj := (← read).aliveRefs.find? r
137
+ | throw s! "RPC reference '{ r} ' is not valid"
138
+ let some val := referencedObj.obj.get? α
139
+ | throw <| s! "RPC call type mismatch in reference '{ r} '\n expected '{ TypeName.typeName α} ', " ++
140
+ s! "got '{ referencedObj.obj.typeName} '"
141
+ return { val, id? := referencedObj.id? }
58
142
59
143
def rpcReleaseRef (r : Lsp.RpcRef) : StateM RpcObjectStore Bool := do
60
144
let st ← get
61
- if st.aliveRefs.contains r then
62
- set { st with aliveRefs := st.aliveRefs.erase r }
63
- return true
145
+ let some referencedObj := st.aliveRefs.find? r
146
+ | return false
147
+ let referencedObj := { referencedObj with rc := referencedObj.rc - 1 }
148
+ if referencedObj.rc == 0 then
149
+ set { st with
150
+ aliveRefs :=
151
+ st.aliveRefs.erase r
152
+ refsById :=
153
+ match referencedObj.id? with
154
+ | none => st.refsById
155
+ | some id => st.refsById.erase id
156
+ }
64
157
else
65
- return false
158
+ set { st with aliveRefs := st.aliveRefs.insert r referencedObj }
159
+ return true
66
160
67
161
/-- `RpcEncodable α` means that `α` can be deserialized from and serialized into JSON
68
162
for the purpose of receiving arguments to and sending return values from
@@ -121,26 +215,11 @@ instance [RpcEncodable α] : RpcEncodable (StateM RpcObjectStore α) where
121
215
let a : α ← rpcDecode j
122
216
return return a
123
217
124
- /-- Marks values to be encoded as opaque references in RPC packets.
125
-
126
- See the docstring for `RpcEncodable`. -/
127
- structure WithRpcRef (α : Type u) where
128
- val : α
129
- deriving Inhabited
130
-
131
218
instance [TypeName α] : RpcEncodable (WithRpcRef α) :=
132
219
{ rpcEncode, rpcDecode }
133
220
where
134
221
-- separate definitions to prevent inlining
135
- rpcEncode r := toJson <$> rpcStoreRef (.mk r.val)
136
- rpcDecode j := do
137
- let r ← fromJson? j
138
- match (← rpcGetRef r) with
139
- | none => throw s! "RPC reference '{ r} ' is not valid"
140
- | some any =>
141
- if let some obj := any.get? α then
142
- return ⟨obj⟩
143
- else
144
- throw s! "RPC call type mismatch in reference '{ r} '\n expected '{ TypeName.typeName α} ', got '{ any.typeName} '"
222
+ rpcEncode r := toJson <$> rpcStoreRef r
223
+ rpcDecode j := do rpcGetRef α (← fromJson? j)
145
224
146
225
end Lean.Server
0 commit comments