node-rdkafka
node-rdkafka copied to clipboard
Avoid leaking uv_async_t in Dispatcher::Deactivate()
On disconnect, Dispatcher::Deactivate() calls uv_close() to close its active uv_async_t instance and nulls out the corresponding field, but never frees the underlying memory.
To reproduce, run the following with node-rdkafka and librdkafka compiled with ASAN:
const { KafkaConsumer } = require('../');
const consumer = new KafkaConsumer({
'group.id': 'kafka',
'metadata.broker.list': 'localhost:9092',
}, {});
consumer.connect({ timeout: 2000 }, function (err) {
if (err) {
console.error('Error connecting to Kafka:', err);
return;
}
consumer.disconnect();
})
This should report:
Direct leak of 128 byte(s) in 1 object(s) allocated from:
#0 0x7f049c3aa647 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
#1 0x7f0471d872a1 in NodeKafka::Callbacks::Dispatcher::Activate() ../src/callbacks.cc:69
#2 0x7f0471e75c06 in NodeKafka::Workers::KafkaConsumerConnect::HandleOKCallback() ../src/workers.cc:579
#3 0x7f0471dd679c in Nan::AsyncWorker::WorkComplete() ../node_modules/nan/nan.h:2008
#4 0x7f0471dd679c in Nan::AsyncExecuteComplete(uv_work_s*) ../node_modules/nan/nan.h:2365
#5 0x7f0471dd679c in Nan::AsyncExecuteComplete(uv_work_s*, int) ../node_modules/nan/nan.h:2369
#6 0x18bb75c in uv__work_done ../deps/uv/src/threadpool.c:329
#7 0x18bf0b2 in uv__async_io ../deps/uv/src/unix/async.c:176
#8 0x18d3b2a in uv__io_poll ../deps/uv/src/unix/linux.c:1485
#9 0x18bfdd6 in uv_run ../deps/uv/src/unix/core.c:447
#10 0xbc9be5 in node::SpinEventLoopInternal(node::Environment*) (/usr/bin/node+0xbc9be5)
#11 0xd1d920 in node::NodeMainInstance::Run(node::ExitCode*, node::Environment*) [clone .part.0] (/usr/bin/node+0xd1d920)
#12 0xd1e38c in node::NodeMainInstance::Run() (/usr/bin/node+0xd1e38c)
#13 0xc710be in node::Start(int, char**) (/usr/bin/node+0xc710be)
#14 0x7f049bdf0d09 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x23d09)
The libuv documentation says it's only safe to free the underlying memory in a close callback passed to uv_close(), or after such a callback has returned. So, use a unique_ptr with a custom deleter to accomplish this.