@@ -9,6 +9,8 @@ open FsAutoComplete.LspHelpers
99open System
1010open System.Threading .Tasks
1111open FsAutoComplete.Utils
12+ open System.Threading
13+ open IcedTasks
1214
1315
1416type FSharpLspClient ( sendServerNotification : ClientNotificationSender , sendServerRequest : ClientRequestSender ) =
@@ -89,37 +91,77 @@ type FSharpLspClient(sendServerNotification: ClientNotificationSender, sendServe
8991
9092
9193
94+ /// <summary>
95+ /// An awaitable wrapper around a task whose result is disposable. The wrapper is not disposable, so this prevents usage errors like "use _lock = myAsync()" when the appropriate usage should be "use! _lock = myAsync())".
96+ /// </summary>
97+ [<Struct>]
98+ type AwaitableDisposable < 'T when 'T :> IDisposable >( t : Task < 'T >) =
99+ member x.GetAwaiter () = t.GetAwaiter()
100+ member x.AsTask () = t
101+ static member op_Implicit ( source : AwaitableDisposable < 'T >) = source.AsTask()
102+
103+ [<AutoOpen>]
104+ module private SemaphoreSlimExtensions =
105+ // Based on https://gist.github.com/StephenCleary/7dd1c0fc2a6594ba0ed7fb7ad6b590d6
106+ // and https://gist.github.com/brendankowitz/5949970076952746a083054559377e56
107+ type SemaphoreSlim with
108+
109+ member x.LockAsync (? ct : CancellationToken ) =
110+ AwaitableDisposable(
111+ task {
112+ let ct = defaultArg ct CancellationToken.None
113+ let t = x.WaitAsync( ct)
114+
115+ do ! t
116+
117+ return
118+ { new IDisposable with
119+ member _. Dispose() =
120+ // only release if the task completed successfully
121+ // otherwise, we could be releasing a semaphore that was never acquired
122+ if t.Status = TaskStatus.RanToCompletion then
123+ x.Release() |> ignore }
124+ }
125+ )
126+
92127type ServerProgressReport ( lspClient : FSharpLspClient , ? token : ProgressToken ) =
93128
94- let mutable canReportProgress = true
129+ let mutable canReportProgress = false
95130 let mutable endSent = false
96131
132+ let locker = new SemaphoreSlim( 1 , 1 )
133+
97134 member val Token = defaultArg token ( ProgressToken.Second(( Guid.NewGuid() .ToString())))
98135
99136 member x.Begin ( title , ? cancellable , ? message , ? percentage ) =
100- async {
101- let! result = lspClient.WorkDoneProgressCreate x.Token
102-
103- match result with
104- | Ok() -> ()
105- | Error e -> canReportProgress <- false
106-
107- if canReportProgress then
108- do !
109- lspClient.Progress(
110- x.Token,
111- WorkDoneProgressBegin.Create(
112- title,
113- ?cancellable = cancellable,
114- ?message = message,
115- ?percentage = percentage
137+ cancellableTask {
138+ use! __ = fun ct -> locker.LockAsync( ct)
139+
140+ if not endSent then
141+ let! result = lspClient.WorkDoneProgressCreate x.Token
142+
143+ match result with
144+ | Ok() -> canReportProgress <- true
145+ | Error e -> canReportProgress <- false
146+
147+ if canReportProgress then
148+ do !
149+ lspClient.Progress(
150+ x.Token,
151+ WorkDoneProgressBegin.Create(
152+ title,
153+ ?cancellable = cancellable,
154+ ?message = message,
155+ ?percentage = percentage
156+ )
116157 )
117- )
118158 }
119159
120160 member x.Report (? cancellable , ? message , ? percentage ) =
121- async {
122- if canReportProgress then
161+ cancellableTask {
162+ use! __ = fun ct -> locker.LockAsync( ct)
163+
164+ if canReportProgress && not endSent then
123165 do !
124166 lspClient.Progress(
125167 x.Token,
@@ -128,14 +170,18 @@ type ServerProgressReport(lspClient: FSharpLspClient, ?token: ProgressToken) =
128170 }
129171
130172 member x.End (? message ) =
131- async {
132- if canReportProgress && not endSent then
173+ cancellableTask {
174+ use! __ = fun ct -> locker.LockAsync( ct)
175+ let stillNeedsToSend = canReportProgress && not endSent
176+ endSent <- true
177+
178+ if stillNeedsToSend then
133179 do ! lspClient.Progress( x.Token, WorkDoneProgressEnd.Create(? message = message))
134- endSent <- true
135180 }
136181
137182 interface IAsyncDisposable with
138- member x.DisposeAsync () = task { do ! x.End() } |> ValueTask
183+ member x.DisposeAsync () =
184+ task { do ! x.End () ( CancellationToken.None) } |> ValueTask
139185
140186 interface IDisposable with
141187 member x.Dispose () =
0 commit comments