Skip to content

Commit 97250ed

Browse files
committed
Add timeout option for IAdbCommandLineClient
1 parent 33ab62c commit 97250ed

File tree

6 files changed

+55
-52
lines changed

6 files changed

+55
-52
lines changed

AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,8 @@ namespace AdvancedSharpAdbClient.Tests
99
/// <summary>
1010
/// A mock implementation of the <see cref="IAdbCommandLineClient"/> class.
1111
/// </summary>
12-
internal class DummyAdbCommandLineClient : AdbCommandLineClient
12+
internal class DummyAdbCommandLineClient() : AdbCommandLineClient(ServerName)
1313
{
14-
public DummyAdbCommandLineClient() : base(ServerName)
15-
{
16-
}
17-
1814
public AdbCommandLineStatus Version { get; set; }
1915

2016
public bool ServerStarted { get; private set; }
@@ -24,7 +20,7 @@ public DummyAdbCommandLineClient() : base(ServerName)
2420

2521
public override Task<bool> CheckAdbFileExistsAsync(string adbPath, CancellationToken cancellationToken = default) => Task.FromResult(true);
2622

27-
protected override int RunProcess(string filename, string command, ICollection<string> errorOutput, ICollection<string> standardOutput)
23+
protected override int RunProcess(string filename, string command, ICollection<string> errorOutput, ICollection<string> standardOutput, int timeout)
2824
{
2925
if (filename == AdbPath)
3026
{
@@ -55,7 +51,7 @@ protected override int RunProcess(string filename, string command, ICollection<s
5551
protected override async Task<int> RunProcessAsync(string filename, string command, ICollection<string> errorOutput, ICollection<string> standardOutput, CancellationToken cancellationToken = default)
5652
{
5753
await Task.Yield();
58-
return RunProcess(filename, command, errorOutput, standardOutput);
54+
return RunProcess(filename, command, errorOutput, standardOutput, Timeout.Infinite);
5955
}
6056

6157
private static string ServerName => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "adb.exe" : "adb";

AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Diagnostics;
1010
using System.Diagnostics.CodeAnalysis;
1111
using System.IO;
12+
using System.Security.Cryptography;
1213
using System.Threading;
1314

1415
namespace AdvancedSharpAdbClient
@@ -93,8 +94,7 @@ public virtual Task<bool> CheckAdbFileExistsAsync(string adbPath, CancellationTo
9394
/// This value can be <see langword="null"/> if you are not interested in the standard output.</param>
9495
/// <param name="cancellationToken">A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.</param>
9596
/// <returns>A <see cref="Task"/> which represents the asynchronous operation.</returns>
96-
/// <remarks>Use this command only for <c>adb</c> commands that return immediately, such as
97-
/// <c>adb version</c>. This operation times out after 5 seconds.</remarks>
97+
/// <remarks>Use this command only for <c>adb</c> commands that return immediately, such as <c>adb version</c>.</remarks>
9898
/// <exception cref="AdbException">The process exited with an exit code other than <c>0</c>.</exception>
9999
protected async Task RunAdbProcessAsync(string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput, CancellationToken cancellationToken = default)
100100
{
@@ -113,8 +113,7 @@ protected async Task RunAdbProcessAsync(string command, ICollection<string>? err
113113
/// This value can be <see langword="null"/> if you are not interested in the standard output.</param>
114114
/// <param name="cancellationToken">A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.</param>
115115
/// <returns>A <see cref="Task{Int32}"/> which returns the return code of the <c>adb</c> process.</returns>
116-
/// <remarks>Use this command only for <c>adb</c> commands that return immediately, such as
117-
/// <c>adb version</c>. This operation times out after 5 seconds.</remarks>
116+
/// <remarks>Use this command only for <c>adb</c> commands that return immediately, such as <c>adb version</c>.</remarks>
118117
protected async Task<int> RunAdbProcessInnerAsync(string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput, CancellationToken cancellationToken = default)
119118
{
120119
ExceptionExtensions.ThrowIfNull(command);
@@ -124,7 +123,14 @@ protected async Task<int> RunAdbProcessInnerAsync(string command, ICollection<st
124123
/// <summary>
125124
/// Asynchronously runs process, invoking a specific command, and reads the standard output and standard error output.
126125
/// </summary>
127-
/// <returns>The return code of the process.</returns>
126+
/// <param name="filename">The filename of the process to start.</param>
127+
/// <param name="command">The command to invoke, such as <c>version</c> or <c>start-server</c>.</param>
128+
/// <param name="errorOutput">A list in which to store the standard error output. Each line is added as a new entry.
129+
/// This value can be <see langword="null"/> if you are not interested in the standard error.</param>
130+
/// <param name="standardOutput">A list in which to store the standard output. Each line is added as a new entry.
131+
/// This value can be <see langword="null"/> if you are not interested in the standard output.</param>
132+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.</param>
133+
/// <returns>A <see cref="Task{Int32}"/> which returns the return code of the process.</returns>
128134
#if !HAS_PROCESS
129135
[DoesNotReturn]
130136
#endif
@@ -142,33 +148,19 @@ protected virtual async Task<int> RunProcessAsync(string filename, string comman
142148

143149
using Process process = Process.Start(psi) ?? throw new AdbException($"The adb process could not be started. The process returned null when starting {filename} {command}");
144150

145-
#if NET5_0_OR_GREATER
146-
using (CancellationTokenSource completionSource = new(TimeSpan.FromMilliseconds(5000)))
151+
using (CancellationTokenRegistration registration = cancellationToken.Register(process.Kill))
147152
{
148-
try
149-
{
150-
await process.WaitForExitAsync(completionSource.Token).ConfigureAwait(false);
151-
}
152-
catch (OperationCanceledException) when (completionSource.IsCancellationRequested)
153-
{
154-
if (!process.HasExited)
155-
{
156-
process.Kill();
157-
}
158-
}
153+
string standardErrorString = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
154+
string standardOutputString = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
155+
156+
errorOutput?.AddRange(standardErrorString.Split(separator, StringSplitOptions.RemoveEmptyEntries));
157+
standardOutput?.AddRange(standardOutputString.Split(separator, StringSplitOptions.RemoveEmptyEntries));
159158
}
160-
#else
161-
if (!process.WaitForExit(5000))
159+
160+
if (!process.HasExited)
162161
{
163162
process.Kill();
164163
}
165-
#endif
166-
167-
string standardErrorString = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
168-
string standardOutputString = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
169-
170-
errorOutput?.AddRange(standardErrorString.Split(separator, StringSplitOptions.RemoveEmptyEntries));
171-
standardOutput?.AddRange(standardOutputString.Split(separator, StringSplitOptions.RemoveEmptyEntries));
172164

173165
// get the return code from the process
174166
return process.ExitCode;

AdvancedSharpAdbClient/AdbCommandLineClient.cs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Diagnostics;
99
using System.Diagnostics.CodeAnalysis;
1010
using System.IO;
11+
using System.Threading;
1112

1213
namespace AdvancedSharpAdbClient
1314
{
@@ -85,9 +86,9 @@ public AdbCommandLineStatus GetVersion()
8586
}
8687

8788
/// <inheritdoc/>
88-
public void StartServer()
89+
public void StartServer(int timeout = Timeout.Infinite)
8990
{
90-
int status = RunAdbProcessInner("start-server", null, null);
91+
int status = RunAdbProcessInner("start-server", null, null, timeout);
9192
if (status == 0) { return; }
9293

9394
// Starting the adb server failed for whatever reason. This can happen if adb.exe
@@ -97,15 +98,17 @@ public void StartServer()
9798

9899
// Try again. This time, we don't call "Inner", and an exception will be thrown if the start operation fails
99100
// again. We'll let that exception bubble up the stack.
100-
RunAdbProcess("start-server", null, null);
101+
RunAdbProcess("start-server", null, null, timeout);
101102
}
102103

103104
/// <inheritdoc/>
104-
public virtual List<string> ExecuteAdbCommand(string command)
105+
/// <remarks>Use this command only for <c>adb</c> commands that return immediately, such as
106+
/// <c>adb version</c>. This operation times out after 5 seconds.</remarks>
107+
public virtual List<string> ExecuteAdbCommand(string command, int timeout = 5000)
105108
{
106109
List<string> errorOutput = [];
107110
List<string> standardOutput = [];
108-
int status = RunAdbProcessInner(command, errorOutput, standardOutput);
111+
int status = RunAdbProcessInner(command, errorOutput, standardOutput, timeout);
109112
if (errorOutput.Count > 0)
110113
{
111114
string error = StringExtensions.Join(Environment.NewLine, errorOutput!);
@@ -170,12 +173,13 @@ protected virtual void EnsureIsValidAdbFile(string adbPath)
170173
/// This value can be <see langword="null"/> if you are not interested in the standard error.</param>
171174
/// <param name="standardOutput">A list in which to store the standard output. Each line is added as a new entry.
172175
/// This value can be <see langword="null"/> if you are not interested in the standard output.</param>
176+
/// <param name="timeout">The timeout in milliseconds to wait for the <c>adb</c> process to exit.</param>
173177
/// <remarks>Use this command only for <c>adb</c> commands that return immediately, such as
174-
/// <c>adb version</c>. This operation times out after 5 seconds.</remarks>
178+
/// <c>adb version</c>. This operation times out after 5 seconds in default.</remarks>
175179
/// <exception cref="AdbException">The process exited with an exit code other than <c>0</c>.</exception>
176-
protected void RunAdbProcess(string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput)
180+
protected void RunAdbProcess(string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput, int timeout = 5000)
177181
{
178-
int status = RunAdbProcessInner(command, errorOutput, standardOutput);
182+
int status = RunAdbProcessInner(command, errorOutput, standardOutput, timeout);
179183
if (status != 0) { throw new AdbException($"The adb process returned error code {status} when running command {command}"); }
180184
}
181185

@@ -188,13 +192,14 @@ protected void RunAdbProcess(string command, ICollection<string>? errorOutput, I
188192
/// This value can be <see langword="null"/> if you are not interested in the standard error.</param>
189193
/// <param name="standardOutput">A list in which to store the standard output. Each line is added as a new entry.
190194
/// This value can be <see langword="null"/> if you are not interested in the standard output.</param>
195+
/// <param name="timeout">The timeout in milliseconds to wait for the <c>adb</c> process to exit.</param>
191196
/// <returns>The return code of the <c>adb</c> process.</returns>
192197
/// <remarks>Use this command only for <c>adb</c> commands that return immediately, such as
193-
/// <c>adb version</c>. This operation times out after 5 seconds.</remarks>
194-
protected int RunAdbProcessInner(string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput)
198+
/// <c>adb version</c>. This operation times out after 5 seconds in default.</remarks>
199+
protected int RunAdbProcessInner(string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput, int timeout = 5000)
195200
{
196201
ExceptionExtensions.ThrowIfNull(command);
197-
return RunProcess(AdbPath, command, errorOutput, standardOutput);
202+
return RunProcess(AdbPath, command, errorOutput, standardOutput, timeout);
198203
}
199204

200205
/// <summary>
@@ -235,11 +240,18 @@ protected virtual void KillProcess(string processName)
235240
/// <summary>
236241
/// Runs process, invoking a specific command, and reads the standard output and standard error output.
237242
/// </summary>
243+
/// <param name="filename">The filename of the process to start.</param>
244+
/// <param name="command">The command to invoke, such as <c>version</c> or <c>start-server</c>.</param>
245+
/// <param name="errorOutput">A list in which to store the standard error output. Each line is added as a new entry.
246+
/// This value can be <see langword="null"/> if you are not interested in the standard error.</param>
247+
/// <param name="standardOutput">A list in which to store the standard output. Each line is added as a new entry.
248+
/// This value can be <see langword="null"/> if you are not interested in the standard output.</param>
249+
/// <param name="timeout">The timeout in milliseconds to wait for the process to exit.</param>
238250
/// <returns>The return code of the process.</returns>
239251
#if !HAS_PROCESS
240252
[DoesNotReturn]
241253
#endif
242-
protected virtual int RunProcess(string filename, string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput)
254+
protected virtual int RunProcess(string filename, string command, ICollection<string>? errorOutput, ICollection<string>? standardOutput, int timeout)
243255
{
244256
#if HAS_PROCESS
245257
ProcessStartInfo psi = new(filename, command)
@@ -254,7 +266,7 @@ protected virtual int RunProcess(string filename, string command, ICollection<st
254266
using Process process = Process.Start(psi) ?? throw new AdbException($"The adb process could not be started. The process returned null when starting {filename} {command}");
255267

256268
// get the return code from the process
257-
if (!process.WaitForExit(5000))
269+
if (!process.WaitForExit(timeout))
258270
{
259271
process.Kill();
260272
}

AdvancedSharpAdbClient/AdbServer.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Globalization;
88
using System.Net;
99
using System.Net.Sockets;
10+
using System.Threading;
1011

1112
namespace AdvancedSharpAdbClient
1213
{
@@ -199,7 +200,7 @@ public StartServerResult StartServer(string adbPath, bool restartServerIfNewer =
199200
|| (restartServerIfNewer && serverStatus.Version < commandLineVersion))
200201
{
201202
StopServer();
202-
commandLineClient.StartServer();
203+
commandLineClient.StartServer(Timeout.Infinite);
203204
return StartServerResult.RestartedOutdatedDaemon;
204205
}
205206
else
@@ -209,7 +210,7 @@ public StartServerResult StartServer(string adbPath, bool restartServerIfNewer =
209210
}
210211
else
211212
{
212-
commandLineClient.StartServer();
213+
commandLineClient.StartServer(Timeout.Infinite);
213214
return StartServerResult.Started;
214215
}
215216
}

AdvancedSharpAdbClient/Exceptions/DeviceNotFoundException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public DeviceNotFoundException() : base("The device was not found.")
2323
/// Initializes a new instance of the <see cref="DeviceNotFoundException"/> class.
2424
/// </summary>
2525
/// <param name="device">The device.</param>
26-
public DeviceNotFoundException(string? device) : base("The device '" + device + "' was not found.")
26+
public DeviceNotFoundException(string? device) : base($"The device '{device}' was not found.")
2727
{
2828
}
2929

AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ public partial interface IAdbCommandLineClient
2020
/// <summary>
2121
/// Starts the adb server by running the <c>adb start-server</c> command.
2222
/// </summary>
23-
void StartServer();
23+
/// <param name="timeout">The timeout in milliseconds to wait for the <c>adb</c> process to exit.</param>
24+
void StartServer(int timeout);
2425

2526
/// <summary>
2627
/// Runs the <c>adb.exe</c> process, invoking a specific <paramref name="command"/>, and reads the standard output.
2728
/// </summary>
2829
/// <param name="command">The <c>adb.exe</c> command to invoke, such as <c>version</c> or <c>start-server</c>.</param>
30+
/// <param name="timeout">The timeout in milliseconds to wait for the <c>adb</c> process to exit.</param>
2931
/// <return>A list in which to store the standard output. Each line is added as a new entry.</return>
30-
List<string> ExecuteAdbCommand(string command);
32+
List<string> ExecuteAdbCommand(string command, int timeout);
3133

3234
/// <summary>
3335
/// Determines whether the <c>adb.exe</c> file exists.

0 commit comments

Comments
 (0)