Skip to content

Commit c579749

Browse files
committed
Change the default timeout from 10 min to 2 sec
- and fix the problem that the async operations never been timed out on .NET Framework
1 parent 9020c33 commit c579749

File tree

3 files changed

+56
-24
lines changed

3 files changed

+56
-24
lines changed

WhoisClient.NET/IQueryOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal interface IQueryOptions
1313
Encoding Encoding { get; }
1414

1515
/// <summary>
16-
/// Gets a timespan to limit the connection attempt, in seconds. The default value is 600 seconds.
16+
/// Gets a timespan to limit the connection attempt, in milliseconds. The default value is 2000 msec.
1717
/// </summary>
1818
int Timeout { get; }
1919

WhoisClient.NET/WhoisClient.cs

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public class WhoisClient
3333
/// <param name="server">FQDN of whois server (ex."whois.arin.net"). This parameter is optional (default value is null) to determine server automatically.</param>
3434
/// <param name="port">TCP port number to connect whois server. This parameter is optional, and default value is 43.</param>
3535
/// <param name="encoding">Encoding method to decode the result of query. This parameter is optional (default value is null) to using ASCII encoding.</param>
36-
/// <param name="timeout">A timespan to limit the connection attempt, in seconds.</param>
36+
/// <param name="timeout">A timespan to limit the connection attempt, in milliseconds.</param>
3737
/// <param name="retries">The number of times a connection will be attempted.</param>
3838
/// <returns>The strong typed result of query which responded from WHOIS server.</returns>
3939
[Obsolete("Use the 'Query(string query, WhoisQueryOptions options)' instead."), EditorBrowsable(EditorBrowsableState.Never)]
4040
public static WhoisResponse Query(string query, string server = null, int port = 43,
41-
Encoding encoding = null, int timeout = 600, int retries = 10)
41+
Encoding encoding = null, int timeout = 2000, int retries = 10)
4242
{
4343
var options = new WhoisQueryOptions();
4444
options.Server = !string.IsNullOrEmpty(server) ? server : options.Server;
@@ -70,13 +70,13 @@ public static WhoisResponse Query(string query, WhoisQueryOptions options)
7070
/// <param name="server">FQDN of whois server (ex."whois.arin.net"). This parameter is optional (default value is null) to determine server automatically.</param>
7171
/// <param name="port">TCP port number to connect whois server. This parameter is optional, and default value is 43.</param>
7272
/// <param name="encoding">Encoding method to decode the result of query. This parameter is optional (default value is null) to using ASCII encoding.</param>
73-
/// <param name="timeout">A timespan to limit the connection attempt, in seconds.</param>
73+
/// <param name="timeout">A timespan to limit the connection attempt, in milliseconds.</param>
7474
/// <param name="retries">The number of times a connection will be attempted.</param>
7575
/// <param name="token">The token to monitor for cancellation requests.</param>
7676
/// <returns>The strong typed result of query which responded from WHOIS server.</returns>
7777
[Obsolete("Use the 'QueryAsync(string query, WhoisQueryOptions options, CancellationToken token)' instead."), EditorBrowsable(EditorBrowsableState.Never)]
7878
public static async Task<WhoisResponse> QueryAsync(string query, string server = null, int port = 43,
79-
Encoding encoding = null, int timeout = 600, int retries = 10, CancellationToken token = default(CancellationToken))
79+
Encoding encoding = null, int timeout = 2000, int retries = 10, CancellationToken token = default(CancellationToken))
8080
{
8181
var options = new WhoisQueryOptions();
8282
options.Server = !string.IsNullOrEmpty(server) ? server : options.Server;
@@ -234,10 +234,10 @@ private static string GetQueryStatement(EndPoint server, string query)
234234
/// <param name="server">FQDN of whois server (ex."whois.arin.net").</param>
235235
/// <param name="port">TCP port number to connect whois server. This parameter is optional, and default value is 43.</param>
236236
/// <param name="encoding">Encoding method to decode the result of query. This parameter is optional (default value is null) to using ASCII encoding.</param>
237-
/// <param name="timeout">A timespan to limit the connection attempt, in seconds. Function returns empty string if it times out.</param>
237+
/// <param name="timeout">A timespan to limit the connection attempt, in milliseconds. Function returns empty string if it times out.</param>
238238
/// <returns>The raw data decoded by encoding parameter from the WHOIS server that responded, or an empty string if a connection cannot be established.</returns>
239239
[Obsolete("Use the 'RawQuery(string query, WhoisQueryOptions options)' instead."), EditorBrowsable(EditorBrowsableState.Never)]
240-
public static string RawQuery(string query, string server, int port = 43, Encoding encoding = null, int timeout = 600)
240+
public static string RawQuery(string query, string server, int port = 43, Encoding encoding = null, int timeout = 2000)
241241
{
242242
var options = new WhoisQueryOptions();
243243
options.Server = server;
@@ -304,8 +304,8 @@ private static string RawQuery(string query, EndPoint server, IQueryOptions opti
304304
using (var s = tcpClient.GetStream())
305305
{
306306
// Specify the timeouts in milliseconds
307-
s.WriteTimeout = options.Timeout * 1000;
308-
s.ReadTimeout = options.Timeout * 1000;
307+
s.WriteTimeout = options.Timeout;
308+
s.ReadTimeout = options.Timeout;
309309

310310
var queryBytes = Encoding.ASCII.GetBytes(query + "\r\n");
311311
s.Write(queryBytes, 0, queryBytes.Length);
@@ -349,12 +349,12 @@ private static string RawQuery(string query, EndPoint server, IQueryOptions opti
349349
/// <param name="server">FQDN of whois server (ex."whois.arin.net").</param>
350350
/// <param name="port">TCP port number to connect whois server. This parameter is optional, and default value is 43.</param>
351351
/// <param name="encoding">Encoding method to decode the result of query. This parameter is optional (default value is null) to using ASCII encoding.</param>
352-
/// <param name="timeout">A timespan to limit the connection attempt, in seconds. Function returns empty string if it times out.</param>
352+
/// <param name="timeout">A timespan to limit the connection attempt, in milliseconds. Function returns empty string if it times out.</param>
353353
/// <param name="token">The token to monitor for cancellation requests.</param>
354354
/// <returns>The raw data decoded by encoding parameter from the WHOIS server that responded, or an empty string if a connection cannot be established.</returns>
355355
[Obsolete("Use the 'RawQueryAsync(string query, WhoisQueryOptions options, CancellationToken token)' instead."), EditorBrowsable(EditorBrowsableState.Never)]
356356
public static async Task<string> RawQueryAsync(string query, string server, int port = 43,
357-
Encoding encoding = null, int timeout = 600, CancellationToken token = default(CancellationToken))
357+
Encoding encoding = null, int timeout = 2000, CancellationToken token = default(CancellationToken))
358358
{
359359
var options = new WhoisQueryOptions();
360360
options.Server = server;
@@ -410,23 +410,25 @@ private static async Task<string> RawQueryAsync(string query, EndPoint server, I
410410
{
411411
using (var s = tcpClient.GetStream())
412412
{
413-
// Specify the timeouts in milliseconds
414-
s.WriteTimeout = options.Timeout * 1000;
415-
s.ReadTimeout = options.Timeout * 1000;
416-
417-
var queryBytes = Encoding.ASCII.GetBytes(query + "\r\n");
418-
await s.WriteAsync(queryBytes, 0, queryBytes.Length, token).ConfigureAwait(false);
419-
await s.FlushAsync(token).ConfigureAwait(false);
413+
await InvokeAsync(async (cancellationToken) =>
414+
{
415+
var queryBytes = Encoding.ASCII.GetBytes(query + "\r\n");
416+
await s.WriteAsync(queryBytes, 0, queryBytes.Length, cancellationToken).ConfigureAwait(false);
417+
await s.FlushAsync(cancellationToken).ConfigureAwait(false);
418+
}, options.Timeout, token);
420419

421420
const int buffSize = 8192;
422421
var readBuff = new byte[buffSize];
423422
var cbRead = default(int);
424423
do
425424
{
426-
cbRead = await s.ReadAsync(readBuff, 0, buffSize, token).ConfigureAwait(false);
427-
responseBytes.AddRange(readBuff.Take(cbRead));
428-
if (cbRead > 0)
429-
await Task.Delay(100, token).ConfigureAwait(false);
425+
await InvokeAsync(async (cancellationToken) =>
426+
{
427+
cbRead = await s.ReadAsync(readBuff, 0, buffSize, cancellationToken).ConfigureAwait(false);
428+
responseBytes.AddRange(readBuff.Take(cbRead));
429+
if (cbRead > 0)
430+
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
431+
}, options.Timeout, token);
430432
} while (cbRead > 0);
431433

432434
return options.Encoding.GetString(responseBytes.ToArray());
@@ -448,6 +450,36 @@ private static async Task<string> RawQueryAsync(string query, EndPoint server, I
448450
}
449451
}
450452

453+
/// <summary>
454+
/// Invoke an asynchronous action with a timeout and cancellation token.
455+
/// </summary>
456+
/// <param name="action">An asynchronous action to invoke.</param>
457+
/// <param name="timeout">A timeout in milliseconds.</param>
458+
/// <param name="token">A cancellation token.</param>
459+
/// <remarks>
460+
/// On the .NET Framework 4.6.2, a cancellation token won't work in the NetworkStream.SendAsync and ReadAsync method.
461+
/// To avoid this issue, we use the combination of Task.Delay and Task.WhenAny to simulate a cancellation token.
462+
/// </remarks>
463+
private static async Task InvokeAsync(Func<CancellationToken, Task> action, int timeout, CancellationToken token)
464+
{
465+
using (var timeoutCancellation = new CancellationTokenSource(millisecondsDelay: timeout))
466+
using (var linked = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutCancellation.Token))
467+
{
468+
#if NETCOREAPP
469+
await action(linked.Token);
470+
#else
471+
var mainTask = action(linked.Token);
472+
var timeoutTask = Task.Delay(Timeout.Infinite, timeoutCancellation.Token);
473+
var cancellationTask = Task.Delay(Timeout.Infinite, token);
474+
475+
var firstCompletedTask = await Task.WhenAny(mainTask, timeoutTask, cancellationTask);
476+
477+
if (firstCompletedTask == timeoutTask) throw new TimeoutException();
478+
if (firstCompletedTask == cancellationTask) token.ThrowIfCancellationRequested();
479+
#endif
480+
}
481+
}
482+
451483
private static readonly Func<TcpConnectionArgs, Task<TcpClient>> DefaultConnectAsync = async args =>
452484
{
453485
if (string.IsNullOrWhiteSpace(args.Host)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(args.Host));

WhoisClient.NET/WhoisQueryOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ public class WhoisQueryOptions : IQueryOptions
2626
public Encoding Encoding { get; set; } = Encoding.ASCII;
2727

2828
/// <summary>
29-
/// Gets or sets a timespan to limit the connection attempt, in seconds. The default value is 600 seconds.
29+
/// Gets or sets a timespan to limit the connection attempt, in milliseconds. The default value is 2000 msec.
3030
/// </summary>
31-
public int Timeout { get; set; } = 600;
31+
public int Timeout { get; set; } = 2000;
3232

3333
/// <summary>
3434
/// Gets or sets the number of times a connection will be attempted. The default value is 10.

0 commit comments

Comments
 (0)