Skip to content

Commit d753ebb

Browse files
authored
Disable GCHeapCount for net6.0 (#1900)
1 parent 9e63abe commit d753ebb

File tree

4 files changed

+87
-29
lines changed

4 files changed

+87
-29
lines changed

release/package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -551,16 +551,16 @@
551551
},
552552
"FSharp.fsac.gc.heapCount": {
553553
"default": 2,
554-
"markdownDescription": "Limits the number of [heaps](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#the-managed-heap) created by the garbage collector. Applies to server garbage collection only. See [Middle Ground between Server and Workstation GC](https://devblogs.microsoft.com/dotnet/middle-ground-between-server-and-workstation-gc/) for more details. This can allow FSAC to still benefit from Server garbage collection while still limiting the number of heaps. Requires restart.",
554+
"markdownDescription": "Limits the number of [heaps](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#the-managed-heap) created by the garbage collector. Applies to server garbage collection only. See [Middle Ground between Server and Workstation GC](https://devblogs.microsoft.com/dotnet/middle-ground-between-server-and-workstation-gc/) for more details. This can allow FSAC to still benefit from Server garbage collection while still limiting the number of heaps. [Only available on .NET 7 or higher](https://github.com/ionide/ionide-vscode-fsharp/issues/1899#issuecomment-1649009462). Requires restart.",
555555
"type": "integer",
556556
"required": [
557557
"FSharp.fsac.gc.server"
558558
]
559559
},
560-
"Fsharp.fsac.gc.noAffinitize": {
561-
"default": true,
562-
"type": "boolean",
563-
"markdownDescription": "Specifies whether to [affinitize](https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#affinitize) garbage collection threads with processors. To affinitize a GC thread means that it can only run on its specific CPU.. Applies to server garbage collection only. See [GCNoAffinitize](https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcnoaffinitize-element#remarks) for more details. Requires restart.",
560+
"Fsharp.fsac.gc.noAffinitize" : {
561+
"default" : true,
562+
"type" : "boolean",
563+
"markdownDescription": "Specifies whether to [affinitize](https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#affinitize) garbage collection threads with processors. To affinitize a GC thread means that it can only run on its specific CPU.. Applies to server garbage collection only. See [GCNoAffinitize](https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcnoaffinitize-element#remarks) for more details. [Only available on .NET 7 or higher](https://github.com/ionide/ionide-vscode-fsharp/issues/1899#issuecomment-1649009462). Requires restart.",
564564
"required": [
565565
"FSharp.fsac.gc.server"
566566
]
@@ -741,7 +741,7 @@
741741
},
742742
"FSharp.openTelemetry.enabled": {
743743
"default": false,
744-
"description": "Enables OpenTelemetry exporter. See https://opentelemetry.io/docs/reference/specification/protocol/exporter/ for environment variables to configure for the exporter. Requires Restart.",
744+
"markdownDescription": "Enables OpenTelemetry exporter. See [OpenTelemetry Protocol Exporter](https://opentelemetry.io/docs/reference/specification/protocol/exporter/) for environment variables to configure for the exporter. Requires Restart.",
745745
"type": "boolean"
746746
},
747747
"FSharp.pipelineHints.enabled": {
@@ -1748,4 +1748,4 @@
17481748
"url": "https://github.com/ionide/ionide-vscode-fsharp.git"
17491749
},
17501750
"version": "7.8.4"
1751-
}
1751+
}

src/Core/LanguageService.fs

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ module Notifications =
3737
let testDetected = testDetectedEmitter.event
3838

3939
module LanguageService =
40+
open Fable.Import.LanguageServer.Client
41+
42+
let private logger =
43+
ConsoleAndOutputChannelLogger(Some "LanguageService", Level.DEBUG, Some defaultOutputChannel, Some Level.DEBUG)
4044
module Types =
4145
open Fable.Import.VSCode.Vscode
4246
type PlainNotification = { content: string }
@@ -599,7 +603,37 @@ Consider:
599603
let clientOpts =
600604
let opts = createEmpty<Client.LanguageClientOptions>
601605

606+
let mutable initFails = 0
607+
608+
let initializationFailureHandler (error: U3<ResponseError,Exception,obj option>) =
609+
if initFails < 5 then
610+
logger.Error($"Initialization failed: %%", error)
611+
initFails <- initFails + 1
612+
true
613+
else
614+
false
615+
602616

617+
let restarts = new ResizeArray<_>()
618+
let errorHandling = {
619+
620+
new ErrorHandler with
621+
member this.closed(): CloseAction =
622+
restarts.Add(1)
623+
if restarts |> Seq.length < 5 then
624+
CloseAction.Restart
625+
else
626+
627+
logger.Error("Server closed")
628+
CloseAction.DoNotRestart
629+
member this.error(error: Exception, message: Message, count: float): ErrorAction =
630+
logger.Error($"Error from server: {error} {message} {count}")
631+
if count < 3.0 then
632+
ErrorAction.Continue
633+
else
634+
ErrorAction.Shutdown
635+
636+
}
603637

604638
let initOpts = createObj [ "AutomaticWorkspaceInit" ==> false ]
605639

@@ -611,7 +645,12 @@ Consider:
611645
// that's why we need to coerce it here.
612646
opts.documentSelector <- Some selector
613647
opts.synchronize <- Some synch
648+
opts.errorHandler <- Some errorHandling
614649
opts.revealOutputChannelOn <- Some Client.RevealOutputChannelOn.Never
650+
// Worth keeping around for debug purposes
651+
// opts.traceOutputChannel <- Some defaultOutputChannel
652+
// opts.outputChannel <- Some defaultOutputChannel
653+
opts.initializationFailedHandler <- Some (!! initializationFailureHandler)
615654

616655
opts.initializationOptions <- Some !^(Some initOpts)
617656
opts?markdown <- createObj [ "isTrusted" ==> true; "supportHtml" ==> true ]
@@ -810,7 +849,7 @@ Consider:
810849
| pres when Seq.length pres > 0 -> "DOTNET_ROLL_FORWARD_TO_PRERELEASE", box 1
811850
| _ -> () ]
812851

813-
return args, envVariables, fsacPath
852+
return args, envVariables, fsacPath, sdkVersion
814853
}
815854

816855
/// Converts true to 1 and false to 0
@@ -819,18 +858,26 @@ Consider:
819858

820859
let spawnNetCore dotnet : JS.Promise<Executable> =
821860
promise {
822-
let! (fsacDotnetArgs, fsacEnvVars, fsacPath) = discoverDotnetArgs ()
823-
861+
let! (fsacDotnetArgs, fsacEnvVars, fsacPath, sdkVersion) = discoverDotnetArgs ()
862+
// Only set DOTNET_GCHeapCount if we're on .NET 7 or higher
863+
// .NET 6 has some issues with this env var on linux
864+
// https://github.com/ionide/ionide-vscode-fsharp/issues/1899
865+
let versionSupportingEnvVars = (semver.parse (Some (U2.Case1 "7.0.0"))).Value
866+
let isNet7orHigher = semver.cmp(U2.Case2 sdkVersion, Operator.GTE, U2.Case2 versionSupportingEnvVars)
824867
let fsacEnvVars =
825868
[ yield! fsacEnvVars
826-
yield "DOTNET_GCHeapCount", box (gcHeapCount.ToString("X")) // Requires hexadecimal value https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#heap-count
869+
if isNet7orHigher then
870+
// it doesn't really make sense to set GCNoAffinitize without setting GCHeapCount
871+
yield "DOTNET_GCNoAffinitize", box (boolToInt gcNoAffinitize) // https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#affinitize
872+
yield "DOTNET_GCHeapCount", box (gcHeapCount.ToString("X")) // Requires hexadecimal value https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#heap-count
873+
827874
yield "DOTNET_GCConserveMemory", box gcConserveMemory //https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#conserve-memory
828875
yield "DOTNET_GCServer", box (boolToInt gcServer) // https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#workstation-vs-server
829-
yield "DOTNET_GCNoAffinitize", box (boolToInt gcNoAffinitize) // https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector#affinitize
876+
830877
if parallelReferenceResolution then
831878
yield "FCS_ParallelReferenceResolution", box "true" ]
832879

833-
printfn $"""FSAC (NETCORE): '%s{fsacPath}'"""
880+
logger.Debug $"""FSAC (NETCORE): '%s{fsacPath}'"""
834881

835882
let exeOpts = createEmpty<ExecutableOptions>
836883

@@ -929,12 +976,17 @@ Consider:
929976

930977
let start (c: ExtensionContext) =
931978
promise {
932-
let! startOpts = getOptions c
933-
let cl = createClient startOpts
934-
registerCustomNotifications cl
935-
let started = cl.start ()
936-
c.subscriptions.Add(started |> box |> unbox)
937-
return ()
979+
try
980+
let! startOpts = getOptions c
981+
logger.Debug ("F# language server options: %%",startOpts)
982+
let cl = createClient startOpts
983+
registerCustomNotifications cl
984+
let started = cl.start ()
985+
c.subscriptions.Add(started |> box |> unbox)
986+
return ()
987+
with e ->
988+
logger.Error("Error starting F# language server: %%", e)
989+
return raise e
938990
}
939991

940992
let stop () =

src/Core/Logging.fs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,15 @@ module Logging =
6464
let formattedMessage = Util.format (template, args)
6565

6666
let formattedLogLine =
67-
String.Format("[{0:HH:mm:ss} {1,-5}] {2}", DateTime.Now, string level, formattedMessage)
67+
String.Format("[{0:HH:mm:ss} {1,-5}] [{2}] {3}", DateTime.Now, string level, source, formattedMessage)
6868

6969
out.appendLine (formattedLogLine)
7070

71-
let private writeToFile level template args =
71+
let private writeToFile level source template args =
7272
let formattedMessage = Util.format (template, args)
7373

7474
let formattedLogLine =
75-
String.Format("[{0:HH:mm:ss} {1,-5}] {2}\n", DateTime.Now, string level, formattedMessage)
75+
String.Format("[{0:HH:mm:ss} {1,-5}] [{2}] {3}\n", DateTime.Now, string level, source, formattedMessage)
7676
// Only store the 200 last logs
7777
if ionideLogsMemory.Length >= 200 then
7878
ionideLogsMemory <- ionideLogsMemory.Tail @ [ formattedLogLine ]
@@ -94,7 +94,7 @@ module Logging =
9494
if source = Some "IONIDE-FSAC" then
9595
try
9696
if string args.[0] <> "parse" then
97-
writeToFile level template args
97+
writeToFile level source template args
9898
with _ ->
9999
() // Do nothing
100100

@@ -165,3 +165,6 @@ module Logging =
165165
/// https://nodejs.org/api/util.html#util_util_format_format
166166
member this.ErrorOnFailed text (p: JS.Promise<_>) =
167167
p |> Promise.catchEnd (fun err -> this.Error(text + ": %O", err))
168+
169+
170+
let defaultOutputChannel = window.createOutputChannel "Ionide"

src/fsharp.fs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ open Ionide.VSCode.Helpers
1010
open Ionide.VSCode.FSharp
1111
open Node.ChildProcess
1212

13+
let private logger =
14+
ConsoleAndOutputChannelLogger(Some "Main", Level.DEBUG, Some defaultOutputChannel, Some Level.DEBUG)
15+
1316
type Api =
1417
{ ProjectLoadedEvent: Event<DTO.Project>
1518
BuildProject: DTO.Project -> JS.Promise<string>
@@ -27,15 +30,15 @@ let activate (context: ExtensionContext) : JS.Promise<Api> =
2730
try
2831
activationFn ctx
2932
with ex ->
30-
printfn $"Error while activating feature '{label}': {ex}"
33+
logger.Error $"Error while activating feature '{label}': {ex}"
3134
Unchecked.defaultof<_>
3235

3336
LanguageService.start context
34-
|> Promise.catch (fun e -> printfn $"Error activating FSAC: %A{e}") // prevent unhandled rejected promises
37+
|> Promise.catch (fun e -> logger.Error $"Error activating FSAC: %A{e}") // prevent unhandled rejected promises
3538
|> Promise.onSuccess (fun _ ->
3639
let progressOpts = createEmpty<ProgressOptions>
3740
progressOpts.location <- U2.Case1 ProgressLocation.Window
38-
41+
logger.Debug "Activating features"
3942
window.withProgress (
4043
progressOpts,
4144
(fun p ctok ->
@@ -46,7 +49,7 @@ let activate (context: ExtensionContext) : JS.Promise<Api> =
4649
p.report pm
4750

4851
Project.activate context
49-
|> Promise.catch (fun e -> printfn $"Error loading projects: %A{e}")
52+
|> Promise.catch (fun e -> logger.Error $"Error loading projects: %A{e}")
5053
|> Promise.onSuccess (fun _ -> tryActivate "quickinfoproject" QuickInfoProject.activate context)
5154
|> Promise.bind (fun _ ->
5255
if showExplorer then
@@ -56,7 +59,7 @@ let activate (context: ExtensionContext) : JS.Promise<Api> =
5659
Promise.lift None)
5760
|> Promise.bind (fun _ -> tryActivate "analyzers" LanguageService.loadAnalyzers ())
5861
|> Promise.catch (fun e ->
59-
printfn $"Error loading all projects: %A{e}"
62+
logger.Error $"Error loading all projects: %A{e}"
6063

6164
let pm =
6265
{| message = Some "Error loading projects"
@@ -119,7 +122,7 @@ let activate (context: ExtensionContext) : JS.Promise<Api> =
119122
GetProjectLauncher = Project.getLauncher
120123
DebugProject = Debugger.debugProject })
121124
|> Promise.catch (fun e ->
122-
printfn $"Error activating features: %A{e}"
125+
logger.Error $"Error activating features: %A{e}"
123126
Unchecked.defaultof<_>)
124127

125128

0 commit comments

Comments
 (0)