Εκτελώ μια εφαρμογή Express js με socket.io για μια συνομιλία webapp και λαμβάνω το ακόλουθο σφάλμα τυχαία περίπου 5 φορές κατά τη διάρκεια 24h. Η διαδικασία του κόμβου τυλίγεται για πάντα και επανεκκινείται αμέσως.
Το πρόβλημα είναι ότι η επανεκκίνηση express πετάει τους χρήστες μου έξω από τα δωμάτιά τους και κανείς δεν το θέλει αυτό.
Ο διακομιστής ιστού χρησιμοποιείται μεσολάβηση από το HAProxy. Δεν υπάρχουν προβλήματα σταθερότητας των υποδοχών, απλώς χρησιμοποιούνται οι μεταφορές websockets και flashsockets. Δεν μπορώ να το αναπαραγάγω αυτό επίτηδες.
Αυτό είναι το σφάλμα με τον κόμβο 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
ΕΠΕΞΕΡΓΑΣΊΑ (2013-07-22)
Προστέθηκε τόσο ο χειριστής σφαλμάτων του πελάτη socket.io όσο και ο χειριστής εξαιρέσεων που δεν έχουν συλληφθεί. Φαίνεται ότι αυτός πιάνει το σφάλμα:
process.on('uncaughtException', function (err) {
console.error(err.stack);
console.log("Node NOT Exiting...");
});
Οπότε υποψιάζομαι ότι δεν'είναι θέμα socket.io αλλά ένα αίτημα http σε έναν άλλο διακομιστή που κάνω ή μια σύνδεση mysql/redis. Το πρόβλημα είναι ότι η στοίβα σφαλμάτων δεν με βοηθάει να εντοπίσω το πρόβλημα του κώδικα μου. Εδώ είναι η έξοδος του αρχείου καταγραφής:
Error: read ECONNRESET
at errnoException (net.js:900:11)
at TCP.onread (net.js:555:19)
Πώς μπορώ να ξέρω τι το προκαλεί αυτό; Πώς μπορώ να βγάλω περισσότερα από το σφάλμα;
Εντάξει, δεν είναι πολύ φλύαρο αλλά εδώ είναι το stacktrace με το "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] ] } }
Εδώ σερβίρω το αρχείο πολιτικής flash socket:
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)
Μπορεί αυτό να είναι η αιτία;
Ίσως το έχετε ήδη μαντέψει: πρόκειται για σφάλμα σύνδεσης.
"ECONNRESET" σημαίνει ότι η άλλη πλευρά της συνομιλίας TCP έκλεισε απότομα το τέλος της σύνδεσής της. Αυτό πιθανότατα οφείλεται σε ένα ή περισσότερα σφάλματα του πρωτοκόλλου της εφαρμογής. Θα μπορούσατε να κοιτάξετε τα αρχεία καταγραφής του διακομιστή API για να δείτε αν παραπονιέται για κάτι.
Αλλά επειδή ψάχνετε επίσης έναν τρόπο να ελέγξετε το σφάλμα και ενδεχομένως να αποσφαλματώσετε το πρόβλημα, θα πρέπει να ρίξετε μια ματιά στο "How to debug a socket hang up error in NodeJS?" το οποίο δημοσιεύτηκε στο stackoverflow σε σχέση με μια παρόμοια ερώτηση.
Γρήγορη και βρώμικη λύση για την ανάπτυξη:
Χρησιμοποιήστε το longjohn, λαμβάνετε μακρά ίχνη στοίβας που θα περιέχουν τις ασύγχρονες λειτουργίες.
>,
Καθαρή και σωστή λύση:
Τεχνικά, στον κόμβο, κάθε φορά που εκπέμπετε ένα συμβάν 'σφάλμα'
και κανείς δεν το ακούει, θα πετάει. Για να μην το πετάξετε, βάλτε έναν ακροατή σε αυτό και χειριστείτε το μόνοι σας. Με αυτόν τον τρόπο μπορείτε να καταγράψετε το σφάλμα με περισσότερες πληροφορίες.
Για να έχετε έναν ακροατή για μια ομάδα κλήσεων μπορείτε να χρησιμοποιήσετε domains και να πιάσετε και άλλα σφάλματα κατά την εκτέλεση. Βεβαιωθείτε ότι κάθε ασύγχρονη λειτουργία που σχετίζεται με το http(Server/Client) βρίσκεται σε διαφορετικό πλαίσιο domain σε σύγκριση με τα άλλα μέρη του κώδικα, το domain θα ακούει αυτόματα τα συμβάντα
error
και θα τα διαδίδει στον δικό του χειριστή. Έτσι, ακούτε μόνο αυτόν τον χειριστή και λαμβάνετε τα δεδομένα σφάλματος. Παίρνετε επίσης περισσότερες πληροφορίες δωρεάν.
ΕΠΕΞΕΡΓΑΣΊΑ (2013-07-22)
Όπως έγραψα παραπάνω:
"ECONNRESET" σημαίνει ότι η άλλη πλευρά της συνομιλίας TCP έκλεισε απότομα το τέλος της σύνδεσής της. Αυτό πιθανότατα οφείλεται σε ένα ή περισσότερα σφάλματα του πρωτοκόλλου της εφαρμογής. Θα μπορούσατε να κοιτάξετε τα αρχεία καταγραφής του διακομιστή API για να δείτε αν διαμαρτύρεται για κάτι.
Αυτό που θα μπορούσε επίσης να συμβαίνει: σε τυχαίες χρονικές στιγμές, η άλλη πλευρά είναι υπερφορτωμένη και απλώς τερματίζει τη σύνδεση ως αποτέλεσμα. Αν συμβαίνει αυτό, εξαρτάται από το σε τι ακριβώς συνδέεστε...
Αλλά ένα πράγμα'είναι σίγουρο: έχετε πράγματι ένα σφάλμα ανάγνωσης στη σύνδεση TCP που προκαλεί την εξαίρεση. Μπορείτε να το διαπιστώσετε αυτό κοιτάζοντας τον κωδικό σφάλματος που δημοσιεύσατε στην επεξεργασία σας, ο οποίος το επιβεβαιώνει.
Ένας απλός διακομιστής tcp που είχα για την εξυπηρέτηση του αρχείου πολιτικής flash το προκαλούσε αυτό. Μπορώ τώρα να πιάσω το σφάλμα χρησιμοποιώντας έναν χειριστή:
# 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)
Είχα ένα παρόμοιο πρόβλημα όπου οι εφαρμογές άρχισαν να κάνουν σφάλματα μετά από μια αναβάθμιση του Node. Πιστεύω ότι αυτό μπορεί να αποδοθεί στην έκδοση v0.9.10 του Node:
Οι προηγούμενες εκδόσεις δεν έκαναν σφάλματα σε διακοπές από τον πελάτη. Μια διακοπή της σύνδεσης από τον πελάτη πετάει το σφάλμα ECONNRESET στο Node. Πιστεύω ότι αυτή είναι η προβλεπόμενη λειτουργικότητα για το Node, οπότε η διόρθωση (τουλάχιστον για μένα) ήταν ο χειρισμός του σφάλματος, κάτι που πιστεύω ότι κάνατε στις unCaught exceptions. Αν και εγώ το χειρίζομαι στον χειριστή net.socket.
Μπορείτε να το επιδείξετε αυτό:
Φτιάξτε έναν απλό socket server και πάρτε το Node v0.9.9 και v0.9.10.
require('net')
.createServer( function(socket)
{
// no nothing
})
.listen(21, function()
{
console.log('Socket ON')
})
Εκκινήστε τον χρησιμοποιώντας την v0.9.9 και στη συνέχεια προσπαθήστε να κάνετε FTP σε αυτόν τον διακομιστή. Εγώ'χρησιμοποιώ FTP και θύρα 21 μόνο επειδή είμαι σε Windows και έχω έναν FTP client, αλλά δεν έχω πρόχειρο telnet client.
Στη συνέχεια, από την πλευρά του πελάτη, απλά διακόψτε τη σύνδεση. (Εγώ απλά κάνω Ctrl-C).
Θα πρέπει να δείτε ΚΑΝΕΝΑ ΣΦΑΛΜΑ όταν χρησιμοποιείτε το Node v0.9.9, και ΣΦΑΛΜΑ όταν χρησιμοποιείτε το Node v.0.9.10 και πάνω.
Στην παραγωγή, χρησιμοποιώ v.0.10. κάτι και εξακολουθεί να δίνει το σφάλμα. Και πάλι, νομίζω ότι αυτό είναι σκόπιμο και η λύση είναι να χειριστείτε το σφάλμα στον κώδικά σας.