Indeksowanie HttpClient powoduje wyciek pamięci
Pracuję nad implementacją WebCrawler ale mam do czynienia z dziwnym wyciekiem pamięci w ASP.NET HttpClient interfejsu Web API.
Więc Wersja cut down jest tutaj:
[UPDATE 2]
Znalazłem problem i nie wycieka HttpClient. Zobacz moją odpowiedź.
[UPDATE 1]
Dodałem bez efektu:
static void Main(string[] args)
{
int waiting = 0;
const int MaxWaiting = 100;
var httpClient = new HttpClient();
foreach (var link in File.ReadAllLines("links.txt"))
{
while (waiting>=MaxWaiting)
{
Thread.Sleep(1000);
Console.WriteLine("Waiting ...");
}
httpClient.GetAsync(link)
.ContinueWith(t =>
{
try
{
var httpResponseMessage = t.Result;
if (httpResponseMessage.IsSuccessStatusCode)
httpResponseMessage.Content.LoadIntoBufferAsync()
.ContinueWith(t2=>
{
if(t2.IsFaulted)
{
httpResponseMessage.Dispose();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine(t2.Exception);
}
else
{
httpResponseMessage.Content.
ReadAsStringAsync()
.ContinueWith(t3 =>
{
Interlocked.Decrement(ref waiting);
try
{
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(httpResponseMessage.RequestMessage.RequestUri);
string s =
t3.Result;
}
catch (Exception ex3)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(ex3);
}
httpResponseMessage.Dispose();
});
}
}
);
}
catch(Exception e)
{
Interlocked.Decrement(ref waiting);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(e);
}
}
);
Interlocked.Increment(ref waiting);
}
Console.Read();
}
Plik zawierający linki jest dostępny tutaj .
Daje to stałą powstanie pamięci. Analiza pamięci pokazuje wiele bajtów utrzymywanych prawdopodobnie przez AsyncCallback. Zrobiłem wcześniej wiele analiz wycieku pamięci, ale ten wydaje się być na poziomie HttpClient.
Używam C# 4.0, więc nie ma tu asynchronicznych/oczekujących, więc używa się tylko TPL 4.0.
Powyższy kod działa, ale nie jest zoptymalizowany i czasami rzuca furią, ale jest wystarczający do odtworzenia efektu. Chodzi o to, że nie mogę znaleźć żadnego punktu, który mógłby spowodować wyciek pamięci.
4 answers
OK, doszedłem do sedna sprawy. Dzięki @Tugberk, @ Darrel i @youssef za spędzanie czasu na tym.
W zasadzie początkowy problem polegał na tym, że wykonywałem zbyt wiele zadań. To zaczęło zbierać swoje żniwo, więc musiałem ograniczyć to i mieć jakiś stan, aby upewnić się, że liczba jednoczesnych zadań jest ograniczona. jest to w zasadzie duże wyzwanie dla pisania procesów, które muszą używać TPL do planowania zadań. Możemy kontrolować wątki w puli wątków, ale musimy również kontrolować zadania, które tworzymy, więc żaden poziom async/await
w tym nie pomoże.
Udało mi się odtworzyć przeciek tylko kilka razy z tym kodem - innym razem po wyrośnięciu nagle spadnie. Wiem, że w 4.5 była przeróbka GC, więc może tu chodzi o to, że GC nie kopnęło wystarczająco, chociaż patrzyłem na liczniki perf na kolekcjach GC generacji 0, 1 i 2.
Czyli ponowne użycie HttpClient
nie powoduje wycieku pamięci.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-28 10:29:06
Nie jestem dobry w definiowaniu problemów z pamięcią, ale wypróbowałem poniższy kod. Jest w. NET 4.5 i używa również funkcji async / wait w C#. Wydaje się, że zużycie pamięci wynosi około 10-15 MB dla całego procesu(nie jestem pewien, czy widzisz to lepsze zużycie pamięci). Ale jeśli obejrzysz # Gen 0 Kolekcje, # Kolekcje Gen 1 i # Kolekcje Gen 2 liczniki perf, są dość wysokie z poniższym kodem.
Jeśli usuniesz GC.Collect
wywołania poniżej, to przechodzi tam iz powrotem między 30MB - 50MB dla całego procesu. Interesujące jest to, że kiedy uruchamiam Twój kod na moim 4 rdzeniowym komputerze, nie widzę nieprawidłowego zużycia pamięci przez proces. Mam. NET 4.5 zainstalowany na moim komputerze, a jeśli nie, problem może być związany z CLR wewnętrzne. NET 4.0 i jestem pewien, że TPL znacznie poprawiła się na.Net 4.5 W oparciu o wykorzystanie zasobów.
class Program {
static void Main(string[] args) {
ServicePointManager.DefaultConnectionLimit = 500;
CrawlAsync().ContinueWith(task => Console.WriteLine("***DONE!"));
Console.ReadLine();
}
private static async Task CrawlAsync() {
int numberOfCores = Environment.ProcessorCount;
List<string> requestUris = File.ReadAllLines(@"C:\Users\Tugberk\Downloads\links.txt").ToList();
ConcurrentDictionary<int, Tuple<Task, HttpRequestMessage>> tasks = new ConcurrentDictionary<int, Tuple<Task, HttpRequestMessage>>();
List<HttpRequestMessage> requestsToDispose = new List<HttpRequestMessage>();
var httpClient = new HttpClient();
for (int i = 0; i < numberOfCores; i++) {
string requestUri = requestUris.First();
var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
Task task = MakeCall(httpClient, requestMessage);
tasks.AddOrUpdate(task.Id, Tuple.Create(task, requestMessage), (index, t) => t);
requestUris.RemoveAt(0);
}
while (tasks.Values.Count > 0) {
Task task = await Task.WhenAny(tasks.Values.Select(x => x.Item1));
Tuple<Task, HttpRequestMessage> removedTask;
tasks.TryRemove(task.Id, out removedTask);
removedTask.Item1.Dispose();
removedTask.Item2.Dispose();
if (requestUris.Count > 0) {
var requestUri = requestUris.First();
var requestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
Task newTask = MakeCall(httpClient, requestMessage);
tasks.AddOrUpdate(newTask.Id, Tuple.Create(newTask, requestMessage), (index, t) => t);
requestUris.RemoveAt(0);
}
GC.Collect(0);
GC.Collect(1);
GC.Collect(2);
}
httpClient.Dispose();
}
private static async Task MakeCall(HttpClient httpClient, HttpRequestMessage requestMessage) {
Console.WriteLine("**Starting new request for {0}!", requestMessage.RequestUri);
var response = await httpClient.SendAsync(requestMessage).ConfigureAwait(false);
Console.WriteLine("**Request is completed for {0}! Status Code: {1}", requestMessage.RequestUri, response.StatusCode);
using (response) {
if (response.IsSuccessStatusCode){
using (response.Content) {
Console.WriteLine("**Getting the HTML for {0}!", requestMessage.RequestUri);
string html = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Console.WriteLine("**Got the HTML for {0}! Legth: {1}", requestMessage.RequestUri, html.Length);
}
}
else if (response.Content != null) {
response.Content.Dispose();
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-30 00:22:08
Ostatnio zgłoszony "wyciek pamięci" w naszym środowisku QA nauczył nas tego:
Rozważmy stos TCP
Nie zakładaj, że stos TCP może wykonać to, co jest wymagane w czasie "uważanym za właściwe dla aplikacji". Jasne, że możemy oddzielić zadania do woli i po prostu kochamy asycha, ale....
Obserwuj stos TCP
Uruchom NETSTAT, gdy wydaje ci się, że masz wyciek pamięci. Jeśli widzisz Pozostałe sesje lub stany w połowie upieczone, możesz przemyśleć swój projekt zgodnie z HttpClient ponowne wykorzystanie i ograniczenie ilości pracy współbieżnej. Konieczne może być również rozważenie zastosowania równoważenia obciążenia na wielu maszynach.
Sesje Half-baked pojawiają się w NETSTAT z Fin-Waits 1 lub 2 i Time-Waits lub nawet RST-WAIT 1 i 2. Nawet" ustalone " sesje mogą być praktycznie martwe, tylko czekając na przerwy w strzelaniu.
Stos i. NET najprawdopodobniej nie są zepsute
Przeciążenie stosu powoduje usypianie maszyny. Odzyskiwanie zajmuje czas i 99% czasu stos wyzdrowieje. Pamiętaj również, że. NET NIE WYDA zasobów przed upływem czasu i że żaden użytkownik nie ma pełnej kontroli nad GC.
Jeśli zabijesz aplikację i NETSTAT zajmie 5 minut, to całkiem dobry znak, że system jest przytłoczony. Jest to również dobry pokaz tego, jak stos jest niezależny od aplikacji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55
Domyślny HttpClient
przecieka, gdy używasz go jako krótkotrwałego obiektu i tworzysz nowe HttpClients na żądanie.
Tutaj jest reprodukcją tego zachowania.
Jako obejście udało mi się nadal używać HttpClient jako krótkotrwałego obiektu, używając następującego pakietu Nuget zamiast wbudowanego zestawu System.Net.Http
:
https://www.nuget.org/packages/HttpClient
Nie wiem, jakie jest pochodzenie tego pakietu, jednak, jak tylko wspomniałem, wyciek pamięci zniknął. Upewnij się, że usuniesz odniesienie do wbudowanej biblioteki.NET System.Net.Http
i zamiast tego użyjesz pakietu Nuget.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:26