Skip to content

Commit 6c01575

Browse files
Nathan Bairdtyrielv
authored andcommitted
Eliminate conhost.exe from git.exe and hooks process launches
GVFS launches git.exe processes with CreateNoWindow=true (or CREATE_NO_WINDOW in native code). Despite the name, this flag tells Windows to create a new hidden console for each child process, which allocates a conhost.exe instance. For frequent, small git operations (e.g., during prefetch), the per-process conhost creation/teardown overhead is disproportionately large. Changes: - GitProcess.cs: Set CreateNoWindow=false. With UseShellExecute=false and stdout/stderr redirected to pipes, the child inherits the parent's console state. Since GVFS.Mount runs as a service with no console, the child gets no console and no conhost. Also remove the unused redirectStandardError parameter (all callers pass true). - ProcessHelper.cs: Set CreateNoWindow=false unconditionally. When redirectOutput is true, I/O goes through pipes so no console is needed. When redirectOutput is false, the child inherits the parent's console handles, which is correct for terminal contexts and harmless in service contexts (output was already going to an invisible hidden console). - GitHooksLoader.cpp: Use DETACHED_PROCESS instead of CREATE_NO_WINDOW in the no-console branch. Explicitly detaches from any console without allocating a new one. The console branch (user terminal) is unchanged. Verified with edge-case tests across terminal, hidden-console, and fully-detached (DETACHED_PROCESS) parent contexts. All git status, git fetch (prefetch hook), and gvfs health scenarios pass. Assisted-by: Tyrie Vella <tyrielv@gmail.com> Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent 1e748bb commit 6c01575

3 files changed

Lines changed: 23 additions & 6 deletions

File tree

GVFS/GVFS.Common/Git/GitProcess.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -815,16 +815,24 @@ public Result MultiPackIndexRepack(string gitObjectDirectory, string batchSize)
815815
return this.InvokeGitAgainstDotGitFolder($"-c pack.threads=1 -c repack.packKeptObjects=true multi-pack-index repack --object-dir=\"{gitObjectDirectory}\" --batch-size={batchSize} --no-progress");
816816
}
817817

818-
public Process GetGitProcess(string command, string workingDirectory, string dotGitDirectory, bool useReadObjectHook, bool redirectStandardError, string gitObjectsDirectory, bool usePreCommandHook)
818+
public Process GetGitProcess(string command, string workingDirectory, string dotGitDirectory, bool useReadObjectHook, string gitObjectsDirectory, bool usePreCommandHook)
819819
{
820820
ProcessStartInfo processInfo = new ProcessStartInfo(this.gitBinPath);
821821
processInfo.WorkingDirectory = workingDirectory;
822822
processInfo.UseShellExecute = false;
823823
processInfo.RedirectStandardInput = true;
824824
processInfo.RedirectStandardOutput = true;
825-
processInfo.RedirectStandardError = redirectStandardError;
825+
processInfo.RedirectStandardError = true;
826826
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
827-
processInfo.CreateNoWindow = true;
827+
828+
// CreateNoWindow=false avoids allocating a hidden conhost.exe per child
829+
// process. This is safe because both stdout and stderr are redirected via
830+
// pipes, so the child never needs a console for I/O. If a future change
831+
// stops redirecting either stream (to forward output to the parent console
832+
// instead), CreateNoWindow must be set to true for that case — otherwise
833+
// the non-redirected stream inherits the parent's console handle, which
834+
// may be absent when running as a service, causing lost output.
835+
processInfo.CreateNoWindow = false;
828836

829837
processInfo.StandardOutputEncoding = UTF8NoBOM;
830838
processInfo.StandardErrorEncoding = UTF8NoBOM;
@@ -903,7 +911,7 @@ protected virtual Result InvokeGitImpl(
903911
// From https://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx
904912
// To avoid deadlocks, use asynchronous read operations on at least one of the streams.
905913
// Do not perform a synchronous read to the end of both redirected streams.
906-
using (this.executingProcess = this.GetGitProcess(command, workingDirectory, dotGitDirectory, useReadObjectHook, redirectStandardError: true, gitObjectsDirectory: gitObjectsDirectory, usePreCommandHook: usePreCommandHook))
914+
using (this.executingProcess = this.GetGitProcess(command, workingDirectory, dotGitDirectory, useReadObjectHook, gitObjectsDirectory: gitObjectsDirectory, usePreCommandHook: usePreCommandHook))
907915
{
908916
StringBuilder output = new StringBuilder();
909917
StringBuilder errors = new StringBuilder();

GVFS/GVFS.Common/ProcessHelper.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,16 @@ public static ProcessResult Run(string programName, string args, bool redirectOu
1818
processInfo.RedirectStandardOutput = redirectOutput;
1919
processInfo.RedirectStandardError = redirectOutput;
2020
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
21-
processInfo.CreateNoWindow = redirectOutput;
21+
22+
// CreateNoWindow=false avoids allocating a hidden conhost.exe per child
23+
// process. When redirectOutput is true, I/O goes through pipes so no
24+
// console is needed. When redirectOutput is false, the child inherits the
25+
// parent's console handles — this works when the parent has a console
26+
// (e.g., GVFS.Hooks invoked from a terminal), but output is silently lost
27+
// when the parent has no console (e.g., service context). This is
28+
// acceptable because CreateNoWindow=true would only send that output to
29+
// an invisible hidden console instead.
30+
processInfo.CreateNoWindow = false;
2231
processInfo.Arguments = args;
2332

2433
return Run(processInfo);

GVFS/GitHooksLoader/GitHooksLoader.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ int ExecuteHook(const std::wstring &applicationName, wchar_t *hookName, int argc
129129
/* Git disallows stdin from hooks */
130130
si.dwFlags = STARTF_USESTDHANDLES;
131131

132-
creationFlags |= CREATE_NO_WINDOW;
132+
creationFlags |= DETACHED_PROCESS;
133133
}
134134

135135
ZeroMemory(&pi, sizeof(pi));

0 commit comments

Comments
 (0)