Skip to content

Commit e9d4377

Browse files
authored
feat: add safe command termination (#1012)
1 parent 4868ea1 commit e9d4377

File tree

5 files changed

+115
-13
lines changed

5 files changed

+115
-13
lines changed

src/Commands/Command.cs

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ public enum EditorType
3737
public string Args { get; set; } = string.Empty;
3838
public bool RaiseError { get; set; } = true;
3939
public bool TraitErrorAsOutput { get; set; } = false;
40+
public bool Cancelable { get; } = false;
41+
42+
public Command(bool cancelable = false)
43+
{
44+
Cancelable = cancelable;
45+
}
4046

4147
public bool Exec()
4248
{
@@ -109,12 +115,22 @@ public bool Exec()
109115
return false;
110116
}
111117

112-
proc.BeginOutputReadLine();
113-
proc.BeginErrorReadLine();
114-
proc.WaitForExit();
118+
_proc = proc;
119+
120+
int exitCode;
121+
try
122+
{
123+
proc.BeginOutputReadLine();
124+
proc.BeginErrorReadLine();
125+
proc.WaitForExit();
115126

116-
int exitCode = proc.ExitCode;
117-
proc.Close();
127+
exitCode = proc.ExitCode;
128+
proc.Close();
129+
}
130+
finally
131+
{
132+
_proc = null;
133+
}
118134

119135
if (!isCancelled && exitCode != 0)
120136
{
@@ -150,17 +166,37 @@ public ReadToEndResult ReadToEnd()
150166
};
151167
}
152168

153-
var rs = new ReadToEndResult()
169+
_proc = proc;
170+
171+
try
154172
{
155-
StdOut = proc.StandardOutput.ReadToEnd(),
156-
StdErr = proc.StandardError.ReadToEnd(),
157-
};
173+
var rs = new ReadToEndResult()
174+
{
175+
StdOut = proc.StandardOutput.ReadToEnd(),
176+
StdErr = proc.StandardError.ReadToEnd(),
177+
};
158178

159-
proc.WaitForExit();
160-
rs.IsSuccess = proc.ExitCode == 0;
161-
proc.Close();
179+
proc.WaitForExit();
180+
rs.IsSuccess = proc.ExitCode == 0;
181+
proc.Close();
162182

163-
return rs;
183+
return rs;
184+
}
185+
finally
186+
{
187+
_proc = null;
188+
}
189+
}
190+
191+
public void TryCancel()
192+
{
193+
if (!this.Cancelable)
194+
throw new Exception("Command is not cancelable!");
195+
196+
if (_proc is null)
197+
return;
198+
199+
Native.OS.TerminateSafely(_proc);
164200
}
165201

166202
protected virtual void OnReadline(string line)
@@ -226,5 +262,7 @@ private ProcessStartInfo CreateGitStartInfo()
226262

227263
[GeneratedRegex(@"\d+%")]
228264
private static partial Regex REG_PROGRESS();
265+
266+
private Process _proc = null;
229267
}
230268
}

src/Native/Linux.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.IO;
5+
using System.Runtime.InteropServices;
56
using System.Runtime.Versioning;
67

78
using Avalonia;
@@ -11,6 +12,14 @@ namespace SourceGit.Native
1112
[SupportedOSPlatform("linux")]
1213
internal class Linux : OS.IBackend
1314
{
15+
private enum SIGNAL : int
16+
{
17+
TERM = 15
18+
}
19+
20+
[DllImport("c")]
21+
private static extern int kill(int pid, int sig);
22+
1423
public void SetupApp(AppBuilder builder)
1524
{
1625
builder.With(new X11PlatformOptions() { EnableIme = true });
@@ -97,6 +106,12 @@ public void OpenWithDefaultEditor(string file)
97106
}
98107
}
99108

109+
public void TerminateSafely(Process process)
110+
{
111+
if (kill(process.Id, (int)SIGNAL.TERM) == 0)
112+
process.WaitForExit();
113+
}
114+
100115
private string FindExecutable(string filename)
101116
{
102117
var pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;

src/Native/MacOS.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Diagnostics;
44
using System.IO;
5+
using System.Runtime.InteropServices;
56
using System.Runtime.Versioning;
67

78
using Avalonia;
@@ -11,6 +12,14 @@ namespace SourceGit.Native
1112
[SupportedOSPlatform("macOS")]
1213
internal class MacOS : OS.IBackend
1314
{
15+
private enum SIGNAL : int
16+
{
17+
TERM = 15
18+
}
19+
20+
[DllImport("System")]
21+
private static extern int kill(int pid, int sig);
22+
1423
public void SetupApp(AppBuilder builder)
1524
{
1625
builder.With(new MacOSPlatformOptions()
@@ -88,5 +97,11 @@ public void OpenWithDefaultEditor(string file)
8897
{
8998
Process.Start("open", $"\"{file}\"");
9099
}
100+
101+
public void TerminateSafely(Process process)
102+
{
103+
if (kill(process.Id, (int)SIGNAL.TERM) == 0)
104+
process.WaitForExit();
105+
}
91106
}
92107
}

src/Native/OS.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface IBackend
2323
void OpenInFileManager(string path, bool select);
2424
void OpenBrowser(string url);
2525
void OpenWithDefaultEditor(string file);
26+
27+
void TerminateSafely(Process process);
2628
}
2729

2830
public static string DataDir
@@ -168,6 +170,11 @@ public static void OpenWithDefaultEditor(string file)
168170
_backend.OpenWithDefaultEditor(file);
169171
}
170172

173+
public static void TerminateSafely(Process process)
174+
{
175+
_backend.TerminateSafely(process);
176+
}
177+
171178
private static void UpdateGitVersion()
172179
{
173180
if (string.IsNullOrEmpty(_gitExecutable) || !File.Exists(_gitExecutable))

src/Native/Windows.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ internal struct MARGINS
3535
public int cyBottomHeight;
3636
}
3737

38+
private enum CTRL_EVENT : int
39+
{
40+
CTRL_C = 0
41+
}
42+
3843
[DllImport("ntdll.dll")]
3944
private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation);
4045

@@ -53,6 +58,12 @@ internal struct MARGINS
5358
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
5459
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
5560

61+
[DllImport("kernel32.dll")]
62+
public static extern bool SetConsoleCtrlHandler(IntPtr handlerRoutine, bool add);
63+
64+
[DllImport("kernel32.dll")]
65+
private static extern bool GenerateConsoleCtrlEvent(int dwCtrlEvent, int dwProcessGroupId);
66+
5667
public void SetupApp(AppBuilder builder)
5768
{
5869
// Fix drop shadow issue on Windows 10
@@ -212,6 +223,22 @@ public void OpenWithDefaultEditor(string file)
212223
Process.Start(start);
213224
}
214225

226+
public void TerminateSafely(Process process)
227+
{
228+
if (SetConsoleCtrlHandler(IntPtr.Zero, true))
229+
{
230+
try
231+
{
232+
if (GenerateConsoleCtrlEvent((int)CTRL_EVENT.CTRL_C, process.Id))
233+
process.WaitForExit();
234+
}
235+
finally
236+
{
237+
SetConsoleCtrlHandler(IntPtr.Zero, false);
238+
}
239+
}
240+
}
241+
215242
private void FixWindowFrameOnWin10(Window w)
216243
{
217244
var platformHandle = w.TryGetPlatformHandle();

0 commit comments

Comments
 (0)