Uncaught ECONNABORTED socket exception
Details
I'm making an IMAP server in nodejs and ran into a behavior that puzzles me.
Basicaly, Thunderbird sends a command (logout) to which (according to the RFC) :
The server MUST send a BYE untagged response before the (tagged) OK response, and then close the network connection.
Except TB don't seems to wait for the last command and disconnect right after the BYE response which raise a write ECONNABORTED exception.
I'm, of course, listening to error on the client (and server) socket, and that error never makes it to the listener.
If I plug into the socket write callback, the error is indeed present and the same that is raised:
socket.write(data, (err) => {});
Also, with:
process.on("uncaughtException", (err) => { });
I get the exception, and if I don't rethrow it, socket.on("close", hadError => {}) get called with hadError set to true. Yet, socket.on("error") still don't get called.
I found a two-part workaround:
- made the socket write calls into a
Promiseso that I can catch the error at the proper place - silently drop this exception from the
uncaughtExceptionlistener.
That workaround seems wrong on many levels:
- an unexpected client disconnection seems a pretty standard scenario in socket communication and thus should be catchable somewhere. I would have thought that adding a
try/catchclause around thesocket.writewould be the perfect place, but experience proved my theory wrong (just to make it clear, my socket.write is in atry/catchblock that catches nothing) - silencing all socket write errors due to
ECONNABORTEDinuncaughtExceptionwithout any way of knowing if that's acceptable or not seems a bit too lenient
So how/where am I supposed to catch that exception properly?
Answers from SO not solving my issue:
-
Trying to catch error when writing to socket using Node JS: I'm already listening to
on(error)and nothing comes up. Also, exception is raised beforeon('end'). -
Node.js uncaught socket exception- socket is closed:
try/catchdon't catch anything andsocket.writableistrue,socket.destroyedisfalsejust before writing - Gracefully handle uncaught exception in forked process with a socket: I'm not forking anything
Node.js version
16.13.1
Example code
I removed everything unrelated to the socket issue. Also, typescript.
Main server class
class ImapServer {
private server:Server;
private socket:Socket = null;
private sessions:Array<ImapSession> = [];
public constructor() {
this.server = createServer((socket:Socket) => {
this.socket = socket;
});
}
public start() {
this.server.listen(143);
this.server.on("connection", (socket:Socket) => {
this.sessions.push(new ImapSession({
socket,
}));
});
this.server.on("close", () => {
});
this.server.on("end", () => {
})
this.server.on("error", (err) => {
})
}
}
Sessions:
class ImapSession {
private socket: Socket;
private state: ImapSessionState;
public constructor(private options: ImapSessionOptions) {
const socket = this.socket = options.socket;
this.onLine = this.onLine.bind(this);
this.socket.on("end", () => {
});
this.socket.on("close", (hadError) => {
});
this.socket.on("error", (err) => {
});
const lr = createInterface({
input: this.socket
});
lr.on("line", this.onLine);
writelineToSocket(this.socket, "* OK IMAP4 ready");
}
private async onLine(line: string) {
try {
if (cmd.startsWith("logout")) {
await asyncWritelineToSocket(this.socket, `* BYE IMAP4rev2 Server logging out\r\n`);
await asyncWritelineToSocket(this.socket, `97 OK LOGOUT completed`);
}
} catch (err) {
}
}
}
asyncWritelineToSocket:
import { Socket } from "net";
export default function asyncWritelineToSocket(socket: Socket, line: string):Promise<void> {
return new Promise<void>((resolve, reject) => {
try {
if(!socket.writable) {
reject("SOCKET-CLOSED");
return;
}
socket.write(`${line}\r\n`, (err) => {
if(err) {
reject(err);
return;
}
resolve();
});
} catch(err) {
reject(err);
}
});
}
and for good measure, the sync version:
import { Socket } from "net";
export default function writeLineToSocket(socket: Socket, line: string) {
try {
socket.write(`${line}\r\n`, (err) => {
if(err) {
}
});
} catch(err) {
}
}
initialisation in index.ts
const srv= new ImapServer()
srv.start();
Operating system
Windows 10
Scope
runtime
Module and version
net
My recommendation is to treat this process the way websockets does by following this formula:
- A client opens a socket to a listener on a server. If the application is peer-to-peer then the application has both client and server functionality, so which ever side is up first is the server.
- When the socket is up it fires a ready event.
- In the handler specify a handler for data events using the once method:
socket.once("data", tempCallback); - The client sends a data package to the server. This data package could be used to store authentication details but mostly serves to inform the server of any required configuration details specific to the socket.
- The server responds by writing a response, which can be anything you want.
- That response executes the
tempCallbackon the first data handler. At this point the socket is capable of writing and listening for data on each end. IntempCallbackprovide your actual data handler:socket.on("data", dataHandler);
Since the entire process is event oriented on both sides you should not need any try/catch blocks.
There has been no activity on this issue for 11 months. The help repository works best when sustained engagement moves conversation forward. The issue will be closed in 1 month. If you are still experiencing this issue on the latest supported versions of Node.js, please leave a comment.
It seems there has been no activity on this issue for a while, and it is being closed. If you believe this issue should remain open, please leave a comment. If you need further assistance or have questions, you can also search for similar issues on Stack Overflow. Make sure to look at the README file for the most updated links.