diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 6b8c5207cb76bc..bc98b2873d5e22 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -82,6 +82,19 @@ TLSWrap::~TLSWrap() { #ifdef SSL_CTRL_SET_TLSEXT_SERVERNAME_CB sni_context_.Reset(); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB + + // See test/parallel/test-tls-transport-destroy-after-own-gc.js: + // If this TLSWrap is garbage collected, we cannot allow callbacks to be + // called on this stream. + + if (stream_ == nullptr) + return; + stream_->set_destruct_cb({ nullptr, nullptr }); + stream_->set_after_write_cb({ nullptr, nullptr }); + stream_->set_alloc_cb({ nullptr, nullptr }); + stream_->set_read_cb({ nullptr, nullptr }); + stream_->set_destruct_cb({ nullptr, nullptr }); + stream_->Unconsume(); } @@ -545,12 +558,16 @@ uint32_t TLSWrap::UpdateWriteQueueSize(uint32_t write_queue_size) { int TLSWrap::ReadStart() { - return stream_->ReadStart(); + if (stream_ != nullptr) + return stream_->ReadStart(); + return 0; } int TLSWrap::ReadStop() { - return stream_->ReadStop(); + if (stream_ != nullptr) + return stream_->ReadStop(); + return 0; } diff --git a/test/parallel/test-tls-transport-destroy-after-own-gc.js b/test/parallel/test-tls-transport-destroy-after-own-gc.js new file mode 100644 index 00000000000000..46f630982af643 --- /dev/null +++ b/test/parallel/test-tls-transport-destroy-after-own-gc.js @@ -0,0 +1,30 @@ +// Flags: --expose-gc +'use strict'; + +// Regression test for https://github.com/nodejs/node/issues/17475 +// Unfortunately, this tests only "works" reliably when checked with valgrind or +// a similar tool. + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { TLSSocket } = require('tls'); +const makeDuplexPair = require('../common/duplexpair'); + +let { clientSide } = makeDuplexPair(); + +let clientTLS = new TLSSocket(clientSide, { isServer: false }); +// eslint-disable-next-line no-unused-vars +let clientTLSHandle = clientTLS._handle; + +setImmediate(() => { + clientTLS = null; + global.gc(); + clientTLSHandle = null; + global.gc(); + setImmediate(() => { + clientSide = null; + global.gc(); + }); +});