3
3
namespace Microsoft.FSharp.Compiler.SourceCodeServices
4
4
open System
5
5
open System.Diagnostics
6
+ open System.Globalization
6
7
open System.Threading
7
8
open Microsoft.FSharp .Control
8
9
open Microsoft.FSharp .Compiler .Lib
@@ -14,207 +15,90 @@ type internal IReactorOperations =
14
15
15
16
module internal Reactor =
16
17
17
- type ResultOrException < 'TResult > =
18
- | Result of 'TResult
19
- | Exception of System.Exception
20
-
21
18
[<NoEquality; NoComparison>]
22
19
type ReactorCommands =
23
20
/// Kick off a build.
24
- | StartBackgroundOp of ( unit -> bool )
25
- /// Do a bit of work on the given build.
26
- | Step
21
+ | SetBackgroundOp of ( unit -> bool ) option
27
22
/// Do some work not synchronized in the mailbox.
28
23
| Op of string * CancellationToken * ( unit -> unit ) * ( unit -> unit )
29
- /// Stop building after finishing the current unit of work.
30
- | StopBackgroundOp of AsyncReplyChannel < ResultOrException < unit >>
31
- /// Finish building.
32
- | FinishBackgroundOp of AsyncReplyChannel < ResultOrException < unit >>
33
- override rc.ToString () =
34
- match rc with
35
- | StartBackgroundOp _ -> " StartBackgroundOp"
36
- | Step-> " Step"
37
- | Op _ -> " Op"
38
- | StopBackgroundOp _ -> " StopBackgroundOp"
39
- | FinishBackgroundOp _ -> " FinishBackgroundOp"
24
+ /// Finish the background building
25
+ | WaitForBackgroundOpCompletion of AsyncReplyChannel < unit >
26
+ /// Finish all the queued ops
27
+ | CompleteAllQueuedOps of AsyncReplyChannel < unit >
40
28
41
- [<NoEquality; NoComparison>]
42
- type ReactorState =
43
- | Idling
44
- | ActivelyBuilding of ( unit -> bool )
45
- | FinishingBuild of ( unit -> bool ) * AsyncReplyChannel < ResultOrException < unit >>
46
- /// An exception was seen in a prior state. The exception is preserved so it can be thrown back to the calling thread.
47
- | BackgroundError of Exception
48
- override rs.ToString () =
49
- match rs with
50
- | Idling-> " Idling"
51
- | ActivelyBuilding _ -> " ActivelyBuilding"
52
- | FinishingBuild _ -> " FinishingBuild"
53
- | BackgroundError _ -> " BackgroundError"
54
-
55
29
[<AutoSerializable( false ); Sealed>]
56
30
/// There is one global Reactor for the entire language service, no matter how many projects or files
57
31
/// are open.
58
32
type Reactor () =
59
33
// We need to store the culture for the VS thread that is executing now,
60
34
// so that when the reactor picks up a thread from the threadpool we can set the culture
61
- #if SILVERLIGHT
62
- let culture = System.Threading.Thread.CurrentThread.CurrentCulture
63
- #else
64
- let culture = new System.Globalization.CultureInfo( System.Threading.Thread.CurrentThread.CurrentUICulture.LCID)
65
- #endif
66
-
67
- // Post an exception back to FinishingBuild channel.
68
- let UnexpectedFinishingBuild commandName ( channel : AsyncReplyChannel < _ >) =
69
- channel.Reply( Exception ( new Exception( sprintf " [Bug]Did not expect %s during FinishingBuild." commandName)))
70
-
71
- // Kick off a build.
72
- let HandleStartBackgroundOp ( inbox : MailboxProcessor < _ >) build state =
73
- inbox.Post Step
74
- match state with
75
- | ActivelyBuilding _ oldBuild -> ActivelyBuilding build // replace the background build
76
- | Idling -> ActivelyBuilding build // start the background build
77
- | FinishingBuild _ -> state // ignore the request for a new background build
78
- | BackgroundError _ -> state // ignore the request for a new background build until error is reported
79
-
80
- // Stop the build.
81
- let HandleStopBackgroundOp ( channel : AsyncReplyChannel < _ >) state =
82
- match state with
83
- | ActivelyBuilding _ oldBuild -> channel.Reply( Result ())
84
- | Idling -> channel.Reply( Result ())
85
- | FinishingBuild(_, channel) -> UnexpectedFinishingBuild " StopBackgroundOp" channel
86
- | BackgroundError e-> channel.Reply( Exception e)
87
-
88
- Idling
89
-
90
- // Interleave the given operation with other work
91
- let HandleOp op state =
92
- try
93
- op()
94
- state
95
- with e ->
96
- System.Diagnostics.Debug.Assert( false , sprintf " Bug in target of HandleOp: %A : %s \n The most recent error reported to an error scope: %+A \n " ( e.GetType()) e.Message e.StackTrace)
97
- state
98
-
99
- // Do a step in the build.
100
- let HandleStep ( inbox : MailboxProcessor < _ >) state =
101
- match state with
102
- | FinishingBuild( build,_)
103
- | ActivelyBuilding( build) ->
104
-
105
- // Gather any required reply channel.
106
- let replyChannel =
107
- match state with
108
- | Idling | ActivelyBuilding _ | BackgroundError _ -> None
109
- | FinishingBuild(_, channel) -> Some channel
110
-
111
- try
112
- if build() then
113
- // More work
114
- inbox.Post Step
115
- state
116
- else
117
- // Work is done. Reply if there is a channel for it.
118
- match replyChannel with
119
- | Some( replyChannel) -> replyChannel.Reply( Result ())
120
- | None-> ()
121
-
122
- // Switch to idle state.
123
- Idling
124
- with e->
125
- System.Diagnostics.Debug.Assert( false , sprintf " [Bug]Failure in HandleStep: %s " ( e.ToString()))
126
- match replyChannel with
127
- | Some( replyChannel) ->
128
- replyChannel.Reply( Exception e)
129
- Idling
130
- | None-> BackgroundError e
131
-
132
- | Idling -> Idling
133
-
134
- | BackgroundError _ -> state
135
-
136
-
137
- let HandleFinishBackgroundO ( inbox : MailboxProcessor < _ >) ( channel : AsyncReplyChannel < _ >) state =
138
- match state with
139
- | ActivelyBuilding( build) ->
140
- inbox.Post Step
141
- FinishingBuild( build, channel)
142
-
143
- | FinishingBuild(_, channelOld) ->
144
- // Don't expect to get here. If this is required then we need to keep all channels and post back to each
145
- // when the build finishes. For now, throw an exception back.
146
- UnexpectedFinishingBuild " FinishBackgroundOping" channel
147
- UnexpectedFinishingBuild " FinishBackgroundOping" channelOld
148
- Idling
149
-
150
- | Idling ->
151
- channel.Reply( Result ())
152
- Idling
35
+ let culture = new CultureInfo( Thread.CurrentThread.CurrentUICulture.LCID)
153
36
154
- | BackgroundError e ->
155
- // We have a waiting channel to post our exception to.
156
- channel.Reply( Exception e)
157
- Idling
158
-
159
37
/// Mailbox dispatch function.
160
38
let builder =
161
39
MailboxProcessor<_>. Start <| fun inbox ->
162
40
41
+
163
42
// Async workflow which receives messages and dispatches to worker functions.
164
- let rec loop ( state : ReactorState ) =
43
+ let rec loop ( bgOpOpt , onComplete ) =
165
44
async { Debug.WriteLine( " Reactor: receiving..., remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
166
- let! msg = inbox.Receive()
167
- System.Threading.Thread.CurrentThread.CurrentUICulture <- culture
168
-
169
- let newState =
170
- try
171
- match msg with
172
- | StartBackgroundOp build ->
173
- Debug.WriteLine( " Reactor: --> start background, remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
174
- HandleStartBackgroundOp inbox build state
175
- | Step ->
45
+
46
+ // Messages always have priority over the background op.
47
+ let! msg =
48
+ async { match bgOpOpt, onComplete with
49
+ | None, None -> let! msg = inbox.Receive() in return Some msg
50
+ | _ -> return ! inbox.TryReceive( 0 ) }
51
+ Thread.CurrentThread.CurrentUICulture <- culture
52
+
53
+ match msg with
54
+ | Some ( SetBackgroundOp bgOpOpt) ->
55
+ Debug.WriteLine( " Reactor: --> set background op, remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
56
+ return ! loop ( bgOpOpt, onComplete)
57
+ | Some ( Op ( desc, ct, op, ccont)) ->
58
+ if ct.IsCancellationRequested then ccont() else
59
+ Debug.WriteLine( " Reactor: --> {0}, remaining {1}, mem {2}, gc2 {3}" , desc, inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
60
+ let time = System.DateTime.Now
61
+ op()
62
+ let span = System.DateTime.Now - time
63
+ //if span.TotalMilliseconds > 100.0 then
64
+ Debug.WriteLine( " Reactor: <-- {0}, remaining {1}, took {2}ms" , desc, inbox.CurrentQueueLength, span.TotalMilliseconds)
65
+ return ! loop ( bgOpOpt, onComplete)
66
+ | Some ( WaitForBackgroundOpCompletion channel) ->
67
+ Debug.WriteLine( " Reactor: --> wait for background (debug only), remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
68
+ match bgOpOpt with
69
+ | None -> ()
70
+ | Some bgOp -> while bgOp() do ()
71
+ channel.Reply(())
72
+ return ! loop ( None, onComplete)
73
+ | Some ( CompleteAllQueuedOps channel) ->
74
+ Debug.WriteLine( " Reactor: --> stop background work and complete all queued ops, remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
75
+ return ! loop ( None, Some channel)
76
+ | None ->
77
+ match bgOpOpt, onComplete with
78
+ | _, Some onComplete -> onComplete.Reply()
79
+ | Some bgOp, None ->
176
80
Debug.WriteLine( " Reactor: --> background step, remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
177
81
let time = System.DateTime.Now
178
- let res = HandleStep inbox state
82
+ let res = bgOp ()
179
83
let span = System.DateTime.Now - time
180
84
//if span.TotalMilliseconds > 100.0 then
181
85
Debug.WriteLine( " Reactor: <-- background step, remaining {0}, took {1}ms" , inbox.CurrentQueueLength, span.TotalMilliseconds)
182
- res
183
- | Op ( desc, ct, op, ccont) ->
184
- if ct.IsCancellationRequested then ccont(); state else
185
- Debug.WriteLine( " Reactor: --> {0}, remaining {1}, mem {2}, gc2 {3}" , desc, inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
186
- let time = System.DateTime.Now
187
- let res = HandleOp op state
188
- let span = System.DateTime.Now - time
189
- //if span.TotalMilliseconds > 100.0 then
190
- Debug.WriteLine( " Reactor: <-- {0}, remaining {1}, took {2}ms" , desc, inbox.CurrentQueueLength, span.TotalMilliseconds)
191
- res
192
- | StopBackgroundOp channel ->
193
- Debug.WriteLine( " Reactor: --> stop background, remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
194
- HandleStopBackgroundOp channel state
195
- | FinishBackgroundOp channel ->
196
- Debug.WriteLine( " Reactor: --> finish background, remaining {0}, mem {1}, gc2 {2}" , inbox.CurrentQueueLength, GC.GetTotalMemory( false )/ 1000000 L, GC.CollectionCount( 2 ))
197
- HandleFinishBackgroundO inbox channel state
198
- with e ->
199
- Debug.Assert( false , " unexpected failure in reactor loop" )
200
- state
201
-
202
- return ! loop newState
86
+ return ! loop (( if res then None else Some bgOp), onComplete)
87
+ | None, None -> failwith " unreachable, should have used inbox.Receive"
203
88
}
204
- loop Idling
89
+ async {
90
+ while true do
91
+ try
92
+ do ! loop ( None, None)
93
+ with e ->
94
+ Debug.Assert( false , String.Format( " unexpected failure in reactor loop {0}, restarting" , e))
95
+ }
205
96
206
97
207
98
// [Foreground Mailbox Accessors] -----------------------------------------------------------
208
- member r.StartBackgroundOp ( build ) =
99
+ member r.SetBackgroundOp ( build ) =
209
100
Debug.WriteLine( " Reactor: enqueue start background, length {0}" , builder.CurrentQueueLength)
210
- builder.Post( StartBackgroundOp build)
211
-
212
- member r.StopBackgroundOp () =
213
- Debug.WriteLine( " Reactor: enqueue stop background, length {0}" , builder.CurrentQueueLength)
214
- match builder.PostAndReply( fun replyChannel -> StopBackgroundOp( replyChannel)) with
215
- | Result result-> result
216
- | Exception excn->
217
- raise excn
101
+ builder.Post( SetBackgroundOp build)
218
102
219
103
member r.EnqueueOp ( desc , op ) =
220
104
Debug.WriteLine( " Reactor: enqueue {0}, length {1}" , desc, builder.CurrentQueueLength)
@@ -230,9 +114,12 @@ module internal Reactor =
230
114
// This is for testing only
231
115
member r.WaitForBackgroundOpCompletion () =
232
116
Debug.WriteLine( " Reactor: enqueue wait for background, length {0}" , builder.CurrentQueueLength)
233
- match builder.PostAndReply( fun replyChannel -> FinishBackgroundOp( replyChannel)) with
234
- | Result result-> result
235
- | Exception excn-> raise excn
117
+ builder.PostAndReply WaitForBackgroundOpCompletion
118
+
119
+ // This is for testing only
120
+ member r.CompleteAllQueuedOps () =
121
+ Debug.WriteLine( " Reactor: enqueue wait for background, length {0}" , builder.CurrentQueueLength)
122
+ builder.PostAndReply WaitForBackgroundOpCompletion
236
123
237
124
member r.EnqueueAndAwaitOpAsync ( desc , f ) =
238
125
async {
0 commit comments