Jak debugować błąd ECONNRESET w Node.js?

288

Korzystam z aplikacji Express.js za pomocą Socket.io dla aplikacji internetowej do czatu i następujący błąd pojawia się losowo około 5 razy w ciągu 24 godzin. Proces węzła jest zawinięty na zawsze i natychmiast uruchamia się ponownie.

Problem polega na tym, że ponowne uruchomienie Express wykopuje moich użytkowników ze swoich pokoi i nikt tego nie chce.

Serwer WWW jest zarządzany przez HAProxy. Nie ma problemów ze stabilnością gniazd, po prostu używając transportów websockets i flashsockets. Nie mogę tego celowo powielić.

To jest błąd z Węzłem v0.10.11:

    events.js:72
            throw er; // Unhandled 'error' event
                  ^
    Error: read ECONNRESET     //alternatively it s a 'write'
        at errnoException (net.js:900:11)
        at TCP.onread (net.js:555:19)
    error: Forever detected script exited with code: 8
    error: Forever restarting script for 2 time

EDYCJA (2013-07-22)

Dodano zarówno moduł obsługi błędów klienta socket.io, jak i moduł obsługi wyjątków nieprzechwyconych. Wydaje się, że ten łapie błąd:

    process.on('uncaughtException', function (err) {
      console.error(err.stack);
      console.log("Node NOT Exiting...");
    });

Podejrzewam więc, że to nie jest problem z Socket.io, ale żądanie HTTP do innego serwera, który robię, lub połączenie MySQL / Redis. Problem polega na tym, że stos błędów nie pomaga mi zidentyfikować problemu z kodem. Oto dane wyjściowe dziennika:

    Error: read ECONNRESET
        at errnoException (net.js:900:11)
        at TCP.onread (net.js:555:19)

Skąd mam wiedzieć, co to powoduje? Jak uzyskać więcej z błędu?

Ok, niezbyt gadatliwy, ale oto stacktrace z Longjohn:

    Exception caught: Error ECONNRESET
    { [Error: read ECONNRESET]
      code: 'ECONNRESET',
      errno: 'ECONNRESET',
      syscall: 'read',
      __cached_trace__:
       [ { receiver: [Object],
           fun: [Function: errnoException],
           pos: 22930 },
         { receiver: [Object], fun: [Function: onread], pos: 14545 },
         {},
         { receiver: [Object],
           fun: [Function: fireErrorCallbacks],
           pos: 11672 },
         { receiver: [Object], fun: [Function], pos: 12329 },
         { receiver: [Object], fun: [Function: onread], pos: 14536 } ],
      __previous__:
       { [Error]
         id: 1061835,
         location: 'fireErrorCallbacks (net.js:439)',
         __location__: 'process.nextTick',
         __previous__: null,
         __trace_count__: 1,
         __cached_trace__: [ [Object], [Object], [Object] ] } }

Tutaj podaję plik strategii gniazda Flash:

    net = require("net")
    net.createServer( (socket) =>
      socket.write("<?xml version=\"1.0\"?>\n")
      socket.write("<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n")
      socket.write("<cross-domain-policy>\n")
      socket.write("<allow-access-from domain=\"*\" to-ports=\"*\"/>\n")
      socket.write("</cross-domain-policy>\n")
      socket.end()
    ).listen(843)

Czy to może być przyczyna?

Samson
źródło
3
@GottZ może to może pomóc (rozmawiało z kimś pracującym w węźle js) gist.github.com/samsonradu/1b0c6feb438f5a53e30e . Wdrażam dziś program handlujący socket.error i dam ci znać.
Samson,
1
@Got uchwyty socket.error nie pomagają, ale process.on („uncaughtException”) wychwytuje błąd. Oto konsola.log błędu: {[Błąd: odczyt ECONNRESET] kod: „ECONNRESET”, errno: „ECONNRESET”, syscall: „odczyt”}
Samson
1
ECONNRESET może być przyczyną problemu z siecią. Jak wiesz, nie można wychwycić wszystkich wyjątków podczas testowania. Niektóre pojawią się na twoim serwerze produkcyjnym. Będziesz musiał uczynić swój serwer solidnym. Możesz poradzić sobie z usuwaniem sesji, używając Redis jako magazynu. Sprawia, że ​​sesje trwają nawet po awarii serwera węzła.
user568109
1
Dlaczego jest to związane z usuwaniem sesji? W każdym razie są obsługiwane przez Redis.
Samson,
3
Masz co najmniej jedno nasłuchiwanie gniazda TCP, które nie ma ustawionego modułu obsługi. Czas więc sprawdzić, gdzie to jest: D
Moss

Odpowiedzi:

253

Być może już go zgadłeś: to błąd połączenia.

„ECONNRESET” oznacza, że ​​druga strona rozmowy TCP gwałtownie zamknęła swój koniec połączenia. Jest to najprawdopodobniej spowodowane jednym lub kilkoma błędami protokołu aplikacji. Możesz spojrzeć na dzienniki serwera API, aby zobaczyć, czy coś narzeka.

Ale ponieważ szukasz także sposobu sprawdzenia błędu i potencjalnego debugowania problemu, powinieneś rzucić okiem na Jak debugować błąd zawieszenia gniazda w NodeJS? ”, Który został opublikowany w stackoverflow w związku z podobnym pytaniem.

Szybkie i brudne rozwiązanie dla programistów :

Użyj longjohn , otrzymasz ślady długiego stosu, które będą zawierać operacje asynchroniczne.

Czyste i poprawne rozwiązanie : technicznie, w węźle, za każdym razem, gdy emitujesz 'error'wydarzenie i nikt go nie słucha, to rzuci . Aby nie rzucał, połóż na nim słuchacza i sam się nim zajmuj. W ten sposób możesz zarejestrować błąd, podając więcej informacji.

Aby mieć jednego detektora dla grupy połączeń, możesz używać domen, a także wychwytywać inne błędy w czasie wykonywania. Upewnij się, że każda operacja asynchroniczna związana z http (serwer / klient) odbywa się w innym kontekście domeny w porównaniu do innych części kodu, domena automatycznie nasłuchuje errorzdarzeń i propaguje ją do własnego modułu obsługi. Więc słuchasz tylko tego modułu obsługi i otrzymujesz dane błędu. Otrzymasz również więcej informacji za darmo.

EDYCJA (2013-07-22)

Jak napisałem powyżej:

„ECONNRESET” oznacza, że ​​druga strona rozmowy TCP gwałtownie zamknęła swój koniec połączenia. Jest to najprawdopodobniej spowodowane jednym lub kilkoma błędami protokołu aplikacji. Możesz spojrzeć na dzienniki serwera API, aby zobaczyć, czy coś narzeka.

Co może być również prawdą: w przypadkowych momentach druga strona jest przeciążona i w rezultacie po prostu niszczy połączenie. W takim przypadku zależy to od tego, z czym się łączysz…

Ale jedno jest pewne: rzeczywiście masz błąd odczytu połączenia TCP, który powoduje wyjątek. Możesz to zobaczyć, patrząc na kod błędu, który opublikowałeś w swojej edycji, co go potwierdza.

e-sushi
źródło
Nie musi to oznaczać „nagłego zamknięcia”. Zwykle wynika to z zapisu do połączenia, które peer już normalnie zamknął. To spowoduje wydanie RST.
Markiz Lorne
1
@EJP Był dobry powód, dla którego napisałem „nagle”. Błąd (brak ostrzeżenia) oznacza, że ​​połączenie zostało zresetowane przez partnera. Istniejące połączenie zostało przymusowo zamknięte przez zdalnego partnera. Wymuszone zamknięcie jest nagłe, ponieważ jest nieoczekiwane! (Zwykle dzieje się tak, jeśli aplikacja równorzędna na zdalnym komputerze zostanie nagle zatrzymana, komputer zostanie ponownie uruchomiony lub aplikacja równorzędna zastosuje „twarde zamknięcie” na zdalnym gnieździe. Ten błąd może również wystąpić, jeśli połączenie zostanie przerwane z powodu aktywności „utrzymywania aktywności” wykrywanie awarii podczas trwającej co najmniej jednej operacji… te i kolejne operacje zakończą się niepowodzeniem.)
e-sushi
2
Ten błąd pojawia się, gdy wsadowo wysyłam około 100 wywołań interfejsu API w pobliżu jednocześnie z przeglądarki (Chrome) w celu przetestowania. Wyobrażam sobie, że Chrome musi zostać przeciążony i zabić niektóre połączenia ... @Samson - co jest złego w przetwarzaniu każdego żądania we własnej domenie i wychwytywaniu błędów domeny bez ponownego uruchamiania serwera?
supershnee
2
@ supershnee Prawie zawsze powinieneś zrestartować serwer po nieprzechwyconym wyjątku, ponieważ dane, aplikacja i plik node.js są w nieznanym stanie. Kontynuowanie po wyjątku stanowi ryzyko dla danych. Jeśli chcesz dowiedzieć się więcej, zapoznaj się z dokumentacją węzła w procesie lub dokumentacją węzła w domenach .
c1moore
39

Powodował to prosty serwer TCP do obsługi pliku zasad Flash. Teraz mogę złapać błąd za pomocą modułu obsługi:

# serving the flash policy file
net = require("net")

net.createServer((socket) =>
  //just added
  socket.on("error", (err) =>
    console.log("Caught flash policy server socket error: ")
    console.log(err.stack)
  )

  socket.write("<?xml version=\"1.0\"?>\n")
  socket.write("<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n")
  socket.write("<cross-domain-policy>\n")
  socket.write("<allow-access-from domain=\"*\" to-ports=\"*\"/>\n")
  socket.write("</cross-domain-policy>\n")
  socket.end()
).listen(843)
Samson
źródło
2
Czy coś jest nie tak z kodem? Czy przed zapisaniem powinienem sprawdzić, czy gniazdo można zapisać?
Samson,
Doh, nie widziałem, że już znalazłeś rozwiązanie, zanim opublikowałem prawie to samo :) Jeśli chodzi o twoje pytanie, nawet jeśli sprawdzisz, czy gniazdo jest do zapisu, może nie być, kiedy napiszesz do niego mikrosekundy później i nadal zgłasza błąd, więc jest to „sposób”, aby się upewnić.
Joachim Isaksson
ok, i czy jest to bezpieczne wyjście? jak socket.close () w module obsługi błędów? ponieważ myślę, że po tych błędach wzrasta obciążenie mojego procesora (nie jestem pewien)
Samson,
2
Zawsze socket.destroy()się upewniałem w module obsługi błędów. Niestety nie mogę znaleźć dokumentacji, czy jest ona wymagana, ale nie powoduje to błędu.
Joachim Isaksson
socket.destroy () uratował mi dzień, cokolwiek to działa !! Dziękuję Ci!
Firas Abd Alrahman
27

Miałem podobny problem, gdy aplikacje zaczęły występować błędy po aktualizacji Node. Myślę, że można to przypisać do wersji Node v0.9.10 tego elementu:

  • netto: nie pomijaj ECONNRESET (Ben Noordhuis)

Poprzednie wersje nie błędnie informowały o przerwach od klienta. Przerwanie połączenia od klienta powoduje zgłoszenie błędu ECONNRESET w węźle. Wierzę, że jest to zamierzona funkcjonalność dla Węzła, więc poprawka (przynajmniej dla mnie) polegała na obsłudze błędu, który, jak sądzę, zrobiłeś w wyjątkach nieprzechwyconych. Chociaż obsługuję to w module obsługi net.socket.

Możesz to zademonstrować:

Utwórz prosty serwer gniazd i pobierz Node v0.9.9 i v0.9.10.

require('net')
    .createServer( function(socket) 
    {
           // no nothing
    })
    .listen(21, function()
     {
           console.log('Socket ON')
    })

Uruchom go za pomocą wersji 0.9.9, a następnie spróbuj wysłać FTP na ten serwer. Używam FTP i portu 21 tylko dlatego, że korzystam z systemu Windows i mam klienta FTP, ale nie ma pod ręką klienta telnet.

Następnie ze strony klienta po prostu przerwij połączenie. (Właśnie wykonuję Ctrl + C)

Powinieneś zobaczyć BRAK BŁĘDU podczas korzystania z Węzła w wersji 0.9.9 i BŁĄD podczas używania Węzła w wersji 0.9.10 i nowszych.

W produkcji używam wersji v0.10. coś i nadal daje błąd. Ponownie myślę, że jest to zamierzone, a rozwiązaniem jest poradzenie sobie z błędem w kodzie.

John Williams
źródło
3
Dzięki, sam go przybiłem! Ważne jest, aby nie dopuścić do rozprzestrzeniania się błędów do uncaughtException, ponieważ powoduje to niestabilność całej aplikacji. Np. Po wykryciu około 10 błędów ECONNRESET serwer czasami przestał odpowiadać (po prostu zamarł i nie obsługiwał żadnych połączeń)
Samson
Wiedziałem również o zmianie wersji węzła, która już nie tłumiła błędu, ale widząc tak wiele problemów pojawiających się i rozwiązywanych w każdej wersji, wolałbym raczej wybrać najnowszą. Używam V0.10.13 teraz btw
Samson
16

Miałem dzisiaj ten sam problem. Po niektórych badań znalazłem bardzo przydatnych --abort-on-uncaught-exceptionopcji node.js . Nie tylko zapewnia dużo bardziej szczegółowe i przydatne śledzenie stosu błędów, ale także zapisuje plik podstawowy po awarii aplikacji, umożliwiając dalsze debugowanie.

Suzana_K
źródło
4
dziwne, że nowa odpowiedź na to stare pytanie powinna pojawić się, gdy patrzę - ale to świetnie, dzięki
Semicolon
13

Napotkałem ten sam problem, ale złagodziłem go, umieszczając:

server.timeout = 0;

przed server.listen. serverjest tutaj serwer HTTP. Domyślny limit czasu wynosi 2 minuty zgodnie z dokumentacją interfejsu API .

Ashish Kaila
źródło
5
To nie jest rozwiązanie, ale szybka poprawka, która psuje rzeczy bez zgłaszania błędu.
Nishant Ghodke,
9

Innym możliwym przypadkiem (ale rzadkim) może być sytuacja, gdy masz komunikację między serwerami i masz server.maxConnectionsbardzo niską wartość.

W rdzeniu biblioteki lib net.js zadzwoni, clientHandle.close()co również spowoduje błąd ECONNRESET:

if (self.maxConnections && self._connections >= self.maxConnections) {
  clientHandle.close(); // causes ECONNRESET on the other end
  return;
}
happy_marmoset
źródło
Świetne połączenie, ale maxConnectionsdomyślna wartość to Infinity. Byłoby tak tylko w przypadku (jak powiedziałeś), jeśli wyraźnie zastąpiłeś tę wartość.
Gajus
7

Tak, podanie pliku zasad może z pewnością spowodować awarię.

Aby powtórzyć, po prostu dodaj opóźnienie do swojego kodu:

net.createServer( function(socket) 
{
    for (i=0; i<1000000000; i++) ;
    socket.write("<?xml version=\"1.0\"?>\n");

… I użyj, telnetaby połączyć się z portem. Jeśli rozłączysz telnet przed upływem opóźnienia, nastąpi awaria (nieprzechwycony wyjątek), gdy socket.write zgłosi błąd.

Aby uniknąć awarii, po prostu dodaj moduł obsługi błędów przed odczytem / zapisem gniazda:

net.createServer(function(socket)
{
    for(i=0; i<1000000000; i++);
    socket.on('error', function() { console.log("error"); });
    socket.write("<?xml version=\"1.0\"?>\n");
}

Gdy spróbujesz powyższego rozłączyć, po prostu otrzymasz komunikat dziennika zamiast awarii.

A kiedy skończysz, pamiętaj o usunięciu opóźnienia.

Joachim Isaksson
źródło
6

Podczas programowania pojawia się również błąd ECONNRESET. Sposób, w jaki go rozwiązuję, polega na tym, że nie uruchamiam serwera za pomocą nodemon, po prostu użyj go, "node server.js"by uruchomić mój serwer, naprawiłem mój problem.

To dziwne, ale zadziałało dla mnie, teraz już nigdy nie widzę błędu ECONNRESET.

Andrew Lam
źródło
4

Miałem również ten błąd i byłem w stanie go rozwiązać po dniach debugowania i analizy:

moje rozwiązanie

Dla mnie problem stanowił VirtualBox (dla Dockera). Miałem Port Forwarding skonfigurowane na mojej maszynie wirtualnej, a błąd wystąpił tylko na przekazanym porcie.

ogólne wnioski

Następujące obserwacje mogą zaoszczędzić Ci dni pracy, które musiałem zainwestować:

  • Dla mnie problem wystąpił tylko w połączeniach z localhost do localhost na jednym porcie. -> sprawdź, zmiana którejkolwiek z tych stałych rozwiązuje problem.
  • Dla mnie problem wystąpił tylko na moim komputerze -> niech ktoś inny spróbuje.
  • Dla mnie problem pojawił się dopiero po pewnym czasie i nie można go wiarygodnie odtworzyć
  • Mój problem nie może zostać sprawdzony za pomocą żadnego z węzłów ani narzędzi (debugujących). -> nie marnuj na to czasu

-> dowiedz się, czy coś nie działa w twojej sieci (ustawienia), takie jak maszyny wirtualne, zapory ogniowe itp., prawdopodobnie jest to przyczyną problemu.

Waog
źródło
2

Rozwiązałem problem, po prostu podłączając się do innej sieci . To jeden z możliwych problemów.

Jak omówiono powyżej, ECONNRESET oznacza, że ​​rozmowa TCP gwałtownie zamknęła koniec połączenia.

Twoje połączenie internetowe może blokować dostęp do niektórych serwerów. W moim przypadku próbowałem połączyć się z mLab (usługa bazy danych w chmurze, która obsługuje bazy danych MongoDB). A mój dostawca usług internetowych to blokuje.

Yousef
źródło
Ten pracował dla mnie, mój kod, który działa dobrze kilka godzin wstecz nagle przestał działać, okazuje się, zmiana sieci spowodowało problem
Aklank Jain
2

Rozwiązałem ten problem przez:

  • Wyłączam połączenie Wi-Fi / Ethernet i włączam.
  • Wpisałem: npm updatew terminalu, aby zaktualizować npm.
  • Próbowałem się wylogować z sesji i zalogować ponownie

Potem wypróbowałem to samo polecenie npm i dobrze, że się udało. Nie byłem pewien, czy to takie proste.

Używam CENTOS 7

Muhammad Tayyab
źródło
0

Miałem ten sam problem i wygląda na to, że problem stanowiła wersja Node.js.

Zainstalowałem poprzednią wersję Node.js (10.14.2) i wszystko było w porządku przy użyciu nvm (pozwala zainstalować kilka wersji Node.js i szybko przełączać się z jednej wersji na inną).

Nie jest to „czyste” rozwiązanie, ale może ci służyć tymczasowo.

Sylvain
źródło
0

Właśnie to rozgryzłem, przynajmniej w moim przypadku użycia.

Dostawałem ECONNRESET. Okazało się, że sposób, w jaki skonfigurowano mojego klienta, bardzo szybko uderzał w serwer wywołaniem API naprawdę wiele razy - i wystarczyło tylko raz trafić punkt końcowy.

Kiedy to naprawiłem, błąd zniknął.

VikR
źródło
-2

Spróbuj dodać te opcje do socket.io:

const options = { transports: ['websocket'], pingTimeout: 3000, pingInterval: 5000 };

Mam nadzieję, że to Ci pomoże !

sol404
źródło