88using EmmyLua . LanguageServer . Framework . Server ;
99using EmmyLua . LanguageServer . Framework . Server . Handler ;
1010using EmmyLua . LanguageServer . Framework . Server . JsonProtocol ;
11+ using EmmyLua . LanguageServer . Framework . Server . Metrics ;
1112using EmmyLua . LanguageServer . Framework . Server . RequestManager ;
1213using EmmyLua . LanguageServer . Framework . Server . Scheduler ;
1314
1415namespace EmmyLua . LanguageServer . Framework ;
1516
16- public abstract class LSPCommunicationBase
17+ public abstract class LSPCommunicationBase : IDisposable
1718{
1819 public JsonSerializerOptions JsonSerializerOptions { get ; } = new ( )
1920 {
@@ -43,6 +44,25 @@ public abstract class LSPCommunicationBase
4344
4445 public ClientCapabilities ClientCapabilities { get ; set ; } = null ! ;
4546
47+ /// <summary>
48+ /// 性能指标收集器
49+ /// </summary>
50+ public IPerformanceMetricsCollector ? MetricsCollector
51+ {
52+ get => _metricsCollector ;
53+ set
54+ {
55+ _metricsCollector = value ;
56+ // 同步到Writer
57+ Writer . MetricsCollector = value ;
58+ }
59+ }
60+ private IPerformanceMetricsCollector ? _metricsCollector ;
61+
62+ protected CancellationTokenSource ? ExitTokenSource { get ; private set ; }
63+ private readonly SemaphoreSlim _exitTokenLock = new ( 1 , 1 ) ;
64+ private bool _disposed ;
65+
4666 protected LSPCommunicationBase ( Stream input , Stream output )
4767 {
4868 Reader = new JsonProtocolReader ( input , JsonSerializerOptions ) ;
@@ -76,6 +96,14 @@ public LSPCommunicationBase AddHandler(IJsonHandler handler)
7696 return this ;
7797 }
7898
99+ /// <summary>
100+ /// 获取当前的性能指标快照
101+ /// </summary>
102+ public PerformanceMetrics ? GetMetrics ( )
103+ {
104+ return MetricsCollector ? . GetMetrics ( ) ;
105+ }
106+
79107 public Task SendNotification ( NotificationMessage notification )
80108 {
81109 Writer . WriteNotification ( notification ) ;
@@ -104,13 +132,18 @@ private async Task OnDispatch(Message message)
104132 {
105133 if ( RequestHandlers . TryGetValue ( request . Method , out var handler ) )
106134 {
135+ MetricsCollector ? . RecordRequestStart ( request . Method ) ;
136+ var startTime = DateTime . UtcNow ;
137+ var success = false ;
138+
107139 try
108140 {
109141 var token = ClientRequestTokenManager . Create ( request . Id ) ;
110142 var result = await handler ( request , token ) . ConfigureAwait ( false ) ;
111143 if ( ! token . IsCancellationRequested )
112144 {
113145 Writer . WriteResponse ( request . Id , result ) ;
146+ success = true ;
114147 }
115148 else
116149 {
@@ -146,6 +179,8 @@ private async Task OnDispatch(Message message)
146179 finally
147180 {
148181 ClientRequestTokenManager . ClearToken ( request . Id ) ;
182+ var duration = DateTime . UtcNow - startTime ;
183+ MetricsCollector ? . RecordRequestComplete ( request . Method , duration , success ) ;
149184 }
150185 }
151186 else
@@ -163,7 +198,23 @@ private async Task OnDispatch(Message message)
163198 {
164199 if ( NotificationHandlers . TryGetValue ( notification . Method , out var handler ) )
165200 {
166- await handler ( notification , CancellationToken . None ) ;
201+ MetricsCollector ? . RecordNotification ( notification . Method ) ;
202+
203+ try
204+ {
205+ // 创建取消令牌以支持通知处理的取消
206+ using var cts = CancellationTokenSource . CreateLinkedTokenSource (
207+ ExitTokenSource ? . Token ?? CancellationToken . None ) ;
208+ await handler ( notification , cts . Token ) ;
209+ }
210+ catch ( OperationCanceledException )
211+ {
212+ // Notification 被取消,忽略
213+ }
214+ catch ( Exception e )
215+ {
216+ Console . Error . WriteLine ( $ "[Notification Handler] Error handling '{ notification . Method } ': { e } ") ;
217+ }
167218 }
168219
169220 break ;
@@ -183,52 +234,106 @@ protected virtual bool BaseHandle(Message message)
183234
184235 public Task Run ( )
185236 {
186- _mutexForExitTokenSource . WaitOne ( ) ;
187- if ( ExitTokenSource is not null )
188- throw new InvalidOperationException ( "Already running." ) ;
189- ExitTokenSource = new CancellationTokenSource ( ) ;
190- var task = Task . Run ( async ( ) =>
237+ _exitTokenLock . Wait ( ) ;
238+ try
191239 {
192- try
240+ if ( ExitTokenSource is not null )
241+ throw new InvalidOperationException ( "Already running." ) ;
242+
243+ ExitTokenSource = new CancellationTokenSource ( ) ;
244+
245+ var task = Task . Run ( async ( ) =>
193246 {
194- while ( ExitTokenSource . IsCancellationRequested is false )
247+ try
195248 {
196- var message = await Reader . ReadAsync ( ExitTokenSource . Token ) ;
197- if ( BaseHandle ( message ) )
249+ while ( ! ExitTokenSource . IsCancellationRequested )
198250 {
199- continue ;
200- }
251+ var message = await Reader . ReadAsync ( ExitTokenSource . Token ) ;
252+ MetricsCollector ? . RecordMessageReceived ( ) ;
253+
254+ if ( BaseHandle ( message ) )
255+ {
256+ continue ;
257+ }
201258
202- Scheduler . Schedule ( OnDispatch , message ) ;
259+ Scheduler . Schedule ( OnDispatch , message ) ;
260+ }
261+ }
262+ catch ( OperationCanceledException )
263+ {
264+ await Console . Error . WriteLineAsync ( "LSPCommunication exited" ) ;
203265 }
266+ catch ( Exception e )
267+ {
268+ Console . Error . WriteLine ( e ) ;
269+ }
270+ finally
271+ {
272+ ExitTokenSource ? . Dispose ( ) ;
273+ ExitTokenSource = null ;
274+ }
275+ } ) ;
276+
277+ return task ;
278+ }
279+ finally
280+ {
281+ _exitTokenLock . Release ( ) ;
282+ }
283+ }
284+
285+ public void Exit ( )
286+ {
287+ _exitTokenLock . Wait ( ) ;
288+ try
289+ {
290+ if ( ExitTokenSource is null )
291+ throw new InvalidOperationException ( "Run() must be called before exit" ) ;
292+ ExitTokenSource . Cancel ( ) ;
293+ }
294+ finally
295+ {
296+ _exitTokenLock . Release ( ) ;
297+ }
298+ }
299+
300+ protected virtual void Dispose ( bool disposing )
301+ {
302+ if ( _disposed ) return ;
303+
304+ if ( disposing )
305+ {
306+ // 取消运行
307+ try
308+ {
309+ Exit ( ) ;
204310 }
205- catch ( OperationCanceledException )
311+ catch
206312 {
207- await Console . Error . WriteLineAsync ( "LSPCommunication exited" ) ;
313+ // Ignore if not running
208314 }
209- catch ( Exception e )
315+
316+ // 释放资源
317+ ExitTokenSource ? . Dispose ( ) ;
318+ _exitTokenLock . Dispose ( ) ;
319+
320+ if ( Scheduler is IDisposable disposableScheduler )
210321 {
211- Console . Error . WriteLine ( e ) ;
322+ disposableScheduler . Dispose ( ) ;
212323 }
213- finally
324+
325+ if ( Writer is IDisposable disposableWriter )
214326 {
215- ExitTokenSource . Dispose ( ) ;
216- ExitTokenSource = null ;
327+ disposableWriter . Dispose ( ) ;
217328 }
218- } ) ;
219- _mutexForExitTokenSource . ReleaseMutex ( ) ;
220- return task ;
221- }
329+ }
222330
223- protected CancellationTokenSource ? ExitTokenSource { get ; private set ; }
224- private readonly Mutex _mutexForExitTokenSource = new ( ) ;
331+ _disposed = true ;
332+ }
225333
226- public void Exit ( )
334+ public void Dispose ( )
227335 {
228- _mutexForExitTokenSource . WaitOne ( ) ;
229- if ( ExitTokenSource is null )
230- throw new InvalidOperationException ( "Run() must be called before exit" ) ;
231- ExitTokenSource ! . Cancel ( ) ;
232- _mutexForExitTokenSource . ReleaseMutex ( ) ;
336+ Dispose ( true ) ;
337+ GC . SuppressFinalize ( this ) ;
233338 }
234339}
0 commit comments