-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NativeAOT] Httpclient timeout #67537
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsDescriptionhttpclient timeout doesn't work well. I specify timeout equals 2 minutes but actually, it's 20 seconds.
Reproduction Steps private async static Task<string> req(NameValueCollection post_data)
{
IDictionary<string, string> dict = new Dictionary<string, string>();
foreach (var k in post_data.AllKeys)
{
dict.Add(k, post_data[k]);
}
using (var postContent = new FormUrlEncodedContent(dict))
using (HttpClient client = new HttpClient())
{
client.Timeout = TimeSpan.FromMinutes(2);
using (HttpResponseMessage response = await client.PostAsync("https://keyauth.win/api/1.0/", postContent))
{
response.EnsureSuccessStatusCode(); // Throw if httpcode is an error
using (HttpContent content = response.Content)
{
return await content.ReadAsStringAsync();
}
}
}
} Expected behaviorwait 120 seconds. Actual behaviorwait 20 seconds. Regression?No response Known WorkaroundsNo response Configuration.NET 6.0 Other informationNo response
|
This looks like a duplicate of #67505. The error message is misleading, but the reason the request failed is |
it's connected successfully without NativeAOT it waits more than 20 seconds. but in NativeAOT, it takes 10 seconds and failed. |
Are you able to reproduce this behavior with a simplified example like: using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync("keyauth.win", 443); |
it's not connect problem, it's Send problem |
Right, and that Send will internally call |
works well. |
The problem is httpclient without NativeAOT wait more than 20 seconds without any problem but in NativeAOT doesn't Wait more than 10 seconds. |
There are two
Where do you set |
Yes, I use the same source and compare between two versions. |
Triage: @AhmedZero can you please post a simple repro with setting up the timeouts which behaves differently with and without NativeAOT? |
sometimes the server has a lot of connections, and that makes the server respond to the client very slow. and I should wait more than 20 seconds. and NativeAOT doesn't wait more than 10 seconds. using System.Security.Cryptography;
using System.Collections.Specialized;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace KeyAuth
{
public class api
{
public string name, ownerid, secret, version;
/// <summary>
/// Set up your application credentials in order to use keyauth
/// </summary>
/// <param name="name">Application Name</param>
/// <param name="ownerid">Your OwnerID, can be found in your account settings.</param>
/// <param name="secret">Application Secret</param>
/// <param name="version">Application Version, if version doesnt match it will open the download link you set up in your application settings and close the app, if empty the app will close</param>
public api(string name, string ownerid, string secret, string version)
{
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(ownerid) || string.IsNullOrWhiteSpace(secret) || string.IsNullOrWhiteSpace(version))
{
error("Application not setup correctly. Please watch video link found in Program.cs");
Environment.Exit(0);
}
this.name = name;
this.ownerid = ownerid;
this.secret = secret;
this.version = version;
}
#region structures
[DataContract]
private class response_structure
{
[DataMember]
public bool success { get; set; }
[DataMember]
public string sessionid { get; set; }
[DataMember]
public string contents { get; set; }
[DataMember]
public string response { get; set; }
[DataMember]
public string message { get; set; }
[DataMember]
public string download { get; set; }
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public user_data_structure info { get; set; }
[DataMember(IsRequired = false, EmitDefaultValue = false)]
public app_data_structure appinfo { get; set; }
[DataMember]
public List<msg> messages { get; set; }
}
public class msg
{
public string message { get; set; }
public string author { get; set; }
public string timestamp { get; set; }
}
[DataContract]
private class user_data_structure
{
[DataMember]
public string username { get; set; }
[DataMember]
public string ip { get; set; }
[DataMember]
public string hwid { get; set; }
[DataMember]
public string createdate { get; set; }
[DataMember]
public string lastlogin { get; set; }
[DataMember]
public List<Data> subscriptions { get; set; } // array of subscriptions (basically multiple user ranks for user with individual expiry dates
}
[DataContract]
private class app_data_structure
{
[DataMember]
public string numUsers { get; set; }
[DataMember]
public string numOnlineUsers { get; set; }
[DataMember]
public string numKeys { get; set; }
[DataMember]
public string version { get; set; }
[DataMember]
public string customerPanelLink { get; set; }
[DataMember]
public string downloadLink { get; set; }
}
#endregion
private string sessionid, enckey;
public bool initzalized;
/// <summary>
/// Initializes the connection with keyauth in order to use any of the functions
/// </summary>
public async Task init()
{
enckey = encryption.sha256(encryption.iv_key());
var init_iv = encryption.sha256(encryption.iv_key());
var values_to_upload = new NameValueCollection
{
["type"] = encryption.byte_arr_to_str(Encoding.Default.GetBytes("init")),
["ver"] = encryption.encrypt(version, secret, init_iv),
["hash"] = checksum(Process.GetCurrentProcess().MainModule.FileName),
["enckey"] = encryption.encrypt(enckey, secret, init_iv),
["name"] = encryption.byte_arr_to_str(Encoding.Default.GetBytes(name)),
["ownerid"] = encryption.byte_arr_to_str(Encoding.Default.GetBytes(ownerid)),
["init_iv"] = init_iv
};
var response = await req(values_to_upload);
if (response == "KeyAuth_Invalid")
{
error("Application not found");
Environment.Exit(0);
}
response = encryption.decrypt(response, secret, init_iv);
var json = response_decoder.string_to_generic<response_structure>(response);
load_response_struct(json);
if (json.success)
{
load_app_data(json.appinfo);
sessionid = json.sessionid;
initzalized = true;
}
else if (json.message == "invalidver")
{
app_data.downloadLink = json.download;
OpenBrowser(app_data.downloadLink);
Environment.Exit(0);
}
}
public static void OpenBrowser(string url)
{
try
{
Process.Start(url);
}
catch
{
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}
#region Checkinit
public static bool IsDebugRelease
{
get
{
#if DEBUG
return true;
#else
return false;
#endif
}
}
public void Checkinit()
{
if (!initzalized)
{
if (IsDebugRelease)
{
error("Not initialized Check if KeyAuthApp.init() does exist");
}
else
{
error("Please initialize first");
}
}
}
#endregion
public static string checksum(string filename)
{
string result;
using (MD5 md = MD5.Create())
{
using (FileStream fileStream = File.OpenRead(filename))
{
byte[] value = md.ComputeHash(fileStream);
result = BitConverter.ToString(value).Replace("-", "").ToLowerInvariant();
}
}
return result;
}
public static void error(string message)
{
Process.Start(new ProcessStartInfo("cmd.exe", $"/c start cmd /C \"color b && title Error && echo {message} && timeout /t 5\"")
{
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
});
Environment.Exit(0);
}
private async static Task<string> req(NameValueCollection post_data)
{
IDictionary<string, string> dict = new Dictionary<string, string>();
foreach (var k in post_data.AllKeys)
{
dict.Add(k, post_data[k]);
}
using (var postContent = new FormUrlEncodedContent(dict))
using (HttpClient client = new HttpClient(new SocketsHttpHandler() { ConnectTimeout = TimeSpan.FromMinutes(2) }))
{
using (HttpResponseMessage response = await client.PostAsync("https://keyauth.win/api/1.0/", postContent))
{
response.EnsureSuccessStatusCode(); // Throw if httpcode is an error
using (HttpContent content = response.Content)
{
return await content.ReadAsStringAsync();
}
}
}
}
#region app_data
public app_data_class app_data = new app_data_class();
public class app_data_class
{
public string numUsers { get; set; }
public string numOnlineUsers { get; set; }
public string numKeys { get; set; }
public string version { get; set; }
public string customerPanelLink { get; set; }
public string downloadLink { get; set; }
}
private void load_app_data(app_data_structure data)
{
app_data.numUsers = data.numUsers;
app_data.numOnlineUsers = data.numOnlineUsers;
app_data.numKeys = data.numKeys;
app_data.version = data.version;
app_data.customerPanelLink = data.customerPanelLink;
}
#endregion
#region user_data
public user_data_class user_data = new user_data_class();
public class user_data_class
{
public string username { get; set; }
public string ip { get; set; }
public string hwid { get; set; }
public string createdate { get; set; }
public string lastlogin { get; set; }
public List<Data> subscriptions { get; set; } // array of subscriptions (basically multiple user ranks for user with individual expiry dates
}
public class Data
{
public string subscription { get; set; }
public string expiry { get; set; }
public string timeleft { get; set; }
}
private void load_user_data(user_data_structure data)
{
user_data.username = data.username;
user_data.ip = data.ip;
user_data.hwid = data.hwid;
user_data.createdate = data.createdate;
user_data.lastlogin = data.lastlogin;
user_data.subscriptions = data.subscriptions; // array of subscriptions (basically multiple user ranks for user with individual expiry dates
}
#endregion
public string expirydaysleft()
{
Checkinit();
System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Local);
dtDateTime = dtDateTime.AddSeconds(long.Parse(user_data.subscriptions[0].expiry)).ToLocalTime();
TimeSpan difference = dtDateTime - DateTime.Now;
return Convert.ToString(difference.Days + " Days " + difference.Hours + " Hours Left");
}
#region response_struct
public response_class response = new response_class();
public class response_class
{
public bool success { get; set; }
public string message { get; set; }
}
private void load_response_struct(response_structure data)
{
response.success = data.success;
response.message = data.message;
}
#endregion
private json_wrapper response_decoder = new json_wrapper(new response_structure());
}
public static class encryption
{
public static string byte_arr_to_str(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.Length * 2);
foreach (byte b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
public static byte[] str_to_byte_arr(string hex)
{
try
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
catch
{
Console.WriteLine("\n\n The session has ended, open program again.");
Thread.Sleep(3500);
Environment.Exit(0);
return null;
}
}
public static string encrypt_string(string plain_text, byte[] key, byte[] iv)
{
Aes encryptor = Aes.Create();
encryptor.Mode = CipherMode.CBC;
encryptor.Key = key;
encryptor.IV = iv;
using (MemoryStream mem_stream = new MemoryStream())
{
using (ICryptoTransform aes_encryptor = encryptor.CreateEncryptor())
{
using (CryptoStream crypt_stream = new CryptoStream(mem_stream, aes_encryptor, CryptoStreamMode.Write))
{
byte[] p_bytes = Encoding.Default.GetBytes(plain_text);
crypt_stream.Write(p_bytes, 0, p_bytes.Length);
crypt_stream.FlushFinalBlock();
byte[] c_bytes = mem_stream.ToArray();
return byte_arr_to_str(c_bytes);
}
}
}
}
public static string decrypt_string(string cipher_text, byte[] key, byte[] iv)
{
Aes encryptor = Aes.Create();
encryptor.Mode = CipherMode.CBC;
encryptor.Key = key;
encryptor.IV = iv;
using (MemoryStream mem_stream = new MemoryStream())
{
using (ICryptoTransform aes_decryptor = encryptor.CreateDecryptor())
{
using (CryptoStream crypt_stream = new CryptoStream(mem_stream, aes_decryptor, CryptoStreamMode.Write))
{
byte[] c_bytes = str_to_byte_arr(cipher_text);
crypt_stream.Write(c_bytes, 0, c_bytes.Length);
crypt_stream.FlushFinalBlock();
byte[] p_bytes = mem_stream.ToArray();
return Encoding.Default.GetString(p_bytes, 0, p_bytes.Length);
}
}
}
}
public static string iv_key() =>
Guid.NewGuid().ToString().Substring(0, Guid.NewGuid().ToString().IndexOf("-", StringComparison.Ordinal));
public static string sha256(string r) =>
byte_arr_to_str(new SHA256Managed().ComputeHash(Encoding.Default.GetBytes(r)));
public static string encrypt(string message, string enc_key, string iv)
{
byte[] _key = Encoding.Default.GetBytes(sha256(enc_key).Substring(0, 32));
byte[] _iv = Encoding.Default.GetBytes(sha256(iv).Substring(0, 16));
return encrypt_string(message, _key, _iv);
}
public static string decrypt(string message, string enc_key, string iv)
{
byte[] _key = Encoding.Default.GetBytes(sha256(enc_key).Substring(0, 32));
byte[] _iv = Encoding.Default.GetBytes(sha256(iv).Substring(0, 16));
return decrypt_string(message, _key, _iv);
}
}
public class json_wrapper
{
public static bool is_serializable(Type to_check) =>
to_check.IsSerializable || to_check.IsDefined(typeof(DataContractAttribute), true);
public json_wrapper(object obj_to_work_with)
{
current_object = obj_to_work_with;
var object_type = current_object.GetType();
serializer = new DataContractJsonSerializer(object_type);
if (!is_serializable(object_type))
throw new Exception($"the object {current_object} isn't a serializable");
}
public object string_to_object(string json)
{
var buffer = Encoding.Default.GetBytes(json);
//SerializationException = session expired
using (var mem_stream = new MemoryStream(buffer))
return serializer.ReadObject(mem_stream);
}
public T string_to_generic<T>(string? json) =>
(T)string_to_object(json);
private DataContractJsonSerializer serializer;
private object current_object;
}
} Program.cs using KeyAuth;
var KeyAuthApp = new api("testest", "testtest", "testtest", "1.0");
await KeyAuthApp.init();
if (KeyAuthApp.initzalized)
Console.WriteLine("Done");
Console.ReadKey(); |
@AhmedZero could you please reduce this to a minimal runnable sample that still reproduces the issue? |
Ok. using System.Collections.Specialized;
await init();
Console.ReadKey();
async Task init()
{
var values_to_upload = new NameValueCollection
{
["init_iv"] = "test"
};
var response = await req(values_to_upload);
}
async static Task<string> req(NameValueCollection post_data)
{
IDictionary<string, string> dict = new Dictionary<string, string>();
foreach (var k in post_data.AllKeys)
{
dict.Add(k, post_data[k]);
}
using (var postContent = new FormUrlEncodedContent(dict))
using (HttpClient client = new HttpClient())
{
client.Timeout = TimeSpan.FromMinutes(2);
using (HttpResponseMessage response = await client.PostAsync("https://keyauth.win/api/1.0/", postContent))
{
response.EnsureSuccessStatusCode(); // Throw if httpcode is an error
using (HttpContent content = response.Content)
{
return await content.ReadAsStringAsync();
}
}
}
} |
Triage: Thanks for the simple repro! |
@AhmedZero What is the NativeAOT compiler package version that you have reproduced it with? Does it still reproduce with the current packages (7.0.0-preview4) ? |
Potential duplicate of dotnet/runtimelab#1883 |
This issue has been marked |
i update to 7.0.0-preview-4.22212.4. and the same problem. that's my csproj. <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>AnyCPU;x64</Platforms>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ImplicitUsings>enable</ImplicitUsings>
<PublishTrimmed>true</PublishTrimmed>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-preview.4.22212.4" />
</ItemGroup>
</Project> |
I have created a simple ASP.NET Core server that takes 200s to respond and pointed your repro to it:
I am not able to reproduce the problem with this setup. Does the problem reproduce for you with this simple server? If yes, how reliable is the repro - does it reproduce every time? |
connect to fake IP like "https://141.95.54.183:444" and use a stopwatch. I don't know if it's a connection timeout or response timeout problem because twice in PostAsync. |
when the server responds to me very fast, there is no problem. when there is a bad connection between the client and the server, the problem occurs. |
Connecting to this fake IP fails for me in ~21.5s. I see the exact same behavior on both regular and NativeAOT flavors. The error message in both cases is:
I believe that it is valid for the request to fail sooner than the set timeout. |
That's right, the request can fail sooner than the |
I don't know why NativeAOT fails after 10 seconds for me? |
You can try packet capture with Wireshark @AhmedZero. And would app without AOT give same or different behavior? AppDomain.CurrentDomain.FirstChanceException += (s, o) => { Console.WriteLine(o.Exception); }; |
the difference between AOT and Not AOT exception:
with AOT
|
Are you sure you are using the same version of .Net for both tests? In .Net 7, we made ConnectTimeout default to 15 seconds, which would explain the difference in behavior. |
yes, but I use .Net 6. |
Can you share the output of Console.WriteLine(System.Diagnostics.FileVersionInfo.GetVersionInfo(typeof(HttpClient).Assembly.Location).ProductVersion); With & without native AOT in your test? |
You are not using .NET 6 runtime with We plan to eliminate the need to reference the ILCompiler package for the 7.0 release and replace with a property that you set to enable AOT compilation, so this sort of version mismatch is going to disappear for 7.0 release.
This would not work with NativeAOT (the IL assemblies do not exist on disk, everything is linked into one binary). It is obvious what it would print: 6.0... without AOT and 7.0... with AOT. Can you please try to run .NET 7 preview SDK from https://github.com/dotnet/installer, bump your project to net7, run it on .NET 7 runtime without AOT and see whether you see the same problem as with AOT? |
In that case the behavior really isn't a mystery here. On 6.0, there is no On 7.0, the default is 15 seconds, so that's the upper bound for a connection attempt. To match 6.0 behavior, increase the |
the same error.
without AOT
|
Now it's not a bug, thanks. |
The change in default ConnectTimeout is getting reverted in #68649 |
Description
httpclient timeout doesn't work well. I specify timeout equals 2 minutes but actually, it's 10 seconds.
Reproduction Steps
Expected behavior
wait 120 seconds.
Actual behavior
wait 10 seconds.
Regression?
No response
Known Workarounds
No response
Configuration
.NET 6.0
Windows 11 X64
Other information
No response
The text was updated successfully, but these errors were encountered: