From ebca3e9cb57c85f2c3a1adbfd226c5fe1ae3e88a Mon Sep 17 00:00:00 2001 From: Guillaume Grossetie Date: Wed, 27 Jul 2022 12:09:00 +0200 Subject: [PATCH 1/2] Emit an reconnectFailure event When the maximum number of backoffs is reached. Error can be undefined. --- Readme.md | 11 ++++++----- lib/TcpConnection.js | 8 ++++---- test/tcpRetyFail.js | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 test/tcpRetyFail.js diff --git a/Readme.md b/Readme.md index 6f6b2dd..812a7dd 100644 --- a/Readme.md +++ b/Readme.md @@ -54,11 +54,12 @@ pino(transport) ### Events -| Name | Callback Signature | Description | -|---------------|----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| -| `open` | `(address: AddressInfo) => void` | Emitted when the TCP or UDP connection is established. | -| `socketError` | `(error: Error) => void` | Emitted when an error occurs on the TCP or UDP socket. The socket won't be closed. | -| `close` | `(hadError: Boolean) => void` | Emitted after the TCP or UDP socket is closed. The argument `hadError` is a boolean which says if the socket was closed due to a transmission error. | +| Name | Callback Signature | Description | +|--------------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| `open` | `(address: AddressInfo) => void` | Emitted when the TCP or UDP connection is established. | +| `socketError` | `(error: Error) => void` | Emitted when an error occurs on the TCP or UDP socket. The socket won't be closed. | +| `close` | `(hadError: Boolean) => void` | Emitted after the TCP or UDP socket is closed. The argument `hadError` is a boolean which says if the socket was closed due to a transmission error. | +| `reconnectFailure` | `(error: Error|undefined) => void` | Emitted when the maximum number of backoffs (i.e., reconnect tries) is reached on a TCP connection. | **IMPORTANT:** In version prior to 6.0, an `error` event was emitted on the writable stream when an error occurs on the TCP or UDP socket. In other words, it was not possible to write data to the writable stream after an error occurs on the TCP or UDP socket. diff --git a/lib/TcpConnection.js b/lib/TcpConnection.js index 4215df6..c193e17 100644 --- a/lib/TcpConnection.js +++ b/lib/TcpConnection.js @@ -130,8 +130,8 @@ module.exports = function factory (userOptions) { } } - function reconnect () { - retryBackoff.backoff() + function reconnect (err) { + retryBackoff.backoff(err) } // end: connection handlers @@ -144,7 +144,7 @@ module.exports = function factory (userOptions) { } } if (options.reconnect) { - reconnect() + reconnect(hadError && socketError) } else { outputStream.emit('close', hadError) } @@ -218,7 +218,7 @@ module.exports = function factory (userOptions) { } }) }) - retry.on('fail', (err) => process.stderr.write(`could not reconnect: ${err.message}`)) + retry.on('fail', (err) => outputStream.emit('reconnectFailure', err)) return retry } diff --git a/test/tcpRetyFail.js b/test/tcpRetyFail.js new file mode 100644 index 0000000..d6aa3ed --- /dev/null +++ b/test/tcpRetyFail.js @@ -0,0 +1,24 @@ +'use strict' +/* eslint-env node, mocha */ + +const { expect } = require('chai') +const TcpConnection = require('../lib/TcpConnection') + +test('tcp retry fail', function testTcpRetryFail (done) { + let socketErrorCount = 0 + const tcpConnection = TcpConnection({ + address: '127.0.0.1', + port: 0, + reconnect: true, + reconnectTries: 2 + } + ) + tcpConnection.on('socketError', () => { + socketErrorCount++ + }) + tcpConnection.on('reconnectFailure', (lastError) => { + expect(socketErrorCount).to.eq(3) + expect(lastError).to.be.an('error') + done() + }) +}) From 3af2250e843939ca07871c432ea029189f89ce80 Mon Sep 17 00:00:00 2001 From: Guillaume Grossetie Date: Fri, 29 Jul 2022 17:13:07 +0200 Subject: [PATCH 2/2] do not reconnect if socket was gracefully closed --- lib/TcpConnection.js | 4 ++-- test/recovery.js | 4 +++- test/tcpBackoff.js | 2 +- test/tcpReconnect.js | 33 +++++++++++++++++++++++++++++++++ test/tcpRetyFail.js | 4 +++- test/tcpSocketError.js | 8 ++++---- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/lib/TcpConnection.js b/lib/TcpConnection.js index c193e17..4ff030f 100644 --- a/lib/TcpConnection.js +++ b/lib/TcpConnection.js @@ -143,8 +143,8 @@ module.exports = function factory (userOptions) { options.onSocketClose(socketError) } } - if (options.reconnect) { - reconnect(hadError && socketError) + if (options.reconnect && hadError) { + reconnect(socketError) } else { outputStream.emit('close', hadError) } diff --git a/test/recovery.js b/test/recovery.js index a989762..8d21e5d 100644 --- a/test/recovery.js +++ b/test/recovery.js @@ -62,7 +62,9 @@ test('recovery', function (done) { // make sure that no number is missing expect(logNumbers).to.deep.eq(Array.from({ length: logNumbers.length }, (_, i) => i + 1)) } finally { - done() + tcpConnection.end(() => { + done() + }) } }) } diff --git a/test/tcpBackoff.js b/test/tcpBackoff.js index 6075a6f..e190b0c 100644 --- a/test/tcpBackoff.js +++ b/test/tcpBackoff.js @@ -22,7 +22,7 @@ test('tcp backoff', function testTcpBackoff (done) { const nextBackoffDelay = exponentialStrategy.next() // initial, 10, 100... next delay should be 1000 expect(nextBackoffDelay).to.eq(1000) - tcpConnection.end('', 'utf8', () => done()) + tcpConnection.end(() => done()) } } }) diff --git a/test/tcpReconnect.js b/test/tcpReconnect.js index 6dcd993..d2f068a 100644 --- a/test/tcpReconnect.js +++ b/test/tcpReconnect.js @@ -133,4 +133,37 @@ test('tcp reconnect after initial failure', async function testTcpReconnectAfter expect(failureCount).to.gte(counter) expect(received.length).to.eq(1) expect(received[0].data.toString('utf8')).to.eq(`log${counter}\n`) + tcpConnection.end() +}) + +test('tcp no reconnect when socket is gracefully closed', function testTcpNoReconnectSocketGracefullyClosed (done) { + let msgCount = 0 + let tcpConnection + const server = startServer({ next }) + function next (msg) { + switch (msg.action) { + case 'started': + connect(msg.address, msg.port) + break + case 'data': + msgCount += 1 + // gracefully close the socket, it should not reconnect + tcpConnection._socket.destroy() + } + } + function connect (address, port) { + tcpConnection = TcpConnection({ + address, + port, + reconnect: true + }) + tcpConnection.write('data', 'utf8', () => { /* ignore */ }) + tcpConnection.on('close', () => { + expect(msgCount).to.eq(1) + // tcp connection should be closed! + server.close(() => { + done() + }) + }) + } }) diff --git a/test/tcpRetyFail.js b/test/tcpRetyFail.js index d6aa3ed..e9aa14b 100644 --- a/test/tcpRetyFail.js +++ b/test/tcpRetyFail.js @@ -19,6 +19,8 @@ test('tcp retry fail', function testTcpRetryFail (done) { tcpConnection.on('reconnectFailure', (lastError) => { expect(socketErrorCount).to.eq(3) expect(lastError).to.be.an('error') - done() + tcpConnection.end(() => { + done() + }) }) }) diff --git a/test/tcpSocketError.js b/test/tcpSocketError.js index fb22df0..d97b8b8 100644 --- a/test/tcpSocketError.js +++ b/test/tcpSocketError.js @@ -19,7 +19,9 @@ test('close connection', function (done) { tcpConnection.write('test', 'utf8', (err) => { // cannot write expect(err.message).to.eq('write after end') - done() + tcpConnection.end(() => { + done() + }) }) }) }) @@ -40,9 +42,7 @@ test('retry connection', function (done) { // TCP connection is still writable expect(tcpConnection.writableEnded).to.eq(false) if (counter === 2) { - tcpConnection.end(() => { - done() - }) + tcpConnection.end(() => done()) } }) })