Try fixing memory leak for syslog input#24525
Conversation
There was a problem hiding this comment.
Pull request overview
This PR attempts to fix a memory leak in the syslog TCP input by improving the management of retained ByteBuf references. The handler now tracks all retained buffers and ensures they are released when the channel closes or an exception occurs, preventing accumulated memory leaks from multi-frame messages.
Key changes:
- Added tracking of retained buffers in a collection for proper lifecycle management
- Implemented cleanup logic in
channelInactive()andexceptionCaught()methods - Added test coverage for the problematic syslog message scenario
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
SyslogTCPFramingRouterHandler.java |
Adds buffer tracking collection and cleanup methods to ensure all retained ByteBuf instances are released |
SyslogTCPFramingRouterHandlerTest.java |
Adds test case for problematic syslog message and debug logging to existing tests |
not_working_syslog_msg_1.txt |
Adds test resource file containing a real-world syslog message that triggers the memory leak scenario |
Comments suppressed due to low confidence (1)
graylog2-server/src/test/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandlerTest.java:1
- The test resource file contains a timestamp in the future (2025-10-31). Test data should use dates in the past to avoid confusion and potential time-based test failures.
/*
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Show resolved
Hide resolved
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Outdated
Show resolved
Hide resolved
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Outdated
Show resolved
Hide resolved
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Outdated
Show resolved
Hide resolved
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Outdated
Show resolved
Hide resolved
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Outdated
Show resolved
Hide resolved
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Outdated
Show resolved
Hide resolved
...2-server/src/test/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandlerTest.java
Outdated
Show resolved
Hide resolved
...2-server/src/test/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandlerTest.java
Outdated
Show resolved
Hide resolved
graylog2-server/src/main/java/org/graylog2/inputs/syslog/tcp/SyslogTCPFramingRouterHandler.java
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| @Override | ||
| public void channelInactive(ChannelHandlerContext ctx) throws Exception { |
There was a problem hiding this comment.
I am bit worried about this part, would memory here potentially grow unlimited if the channel is high active?
There was a problem hiding this comment.
From what I understood, in netty terms a "channel" is basically a single network-connection to a port. So once a single transmission of a message is finished, the channel becomes inactive. OTOH, I'm not sure if remote syslog-logger are keeping the connection open and pumping a gazillion messages in one go.
From a different perspective: The retained ByteBuf's exist in out direct (off-heap) memory already. This is just adding the collection, which keeps track of the ByteBuf-objects, so compared to them, it should be rather cheap memory-wise.
|
One challenge that popped up: |
…rence to already freed ones while reading from an open channel.
|
Tested. I have created a script to send Syslog TCP messages, sent 100K messages to Graylog single node and to Graylog with two nodes. I haven't seen any data loss. Also no memory leaks. |
|
Claude suggests these improvements to the unit tests:
Fix: Use result.readableBytes() for actual, and bytes.length - 1 for expected (only stripping the \n
Fix: Call channel.finishAndReleaseAll() before reassigning, or use a local variable. |
|
More interestingly, Claude also suggests an improvement to address long-lived connections:
|
|
The memory leak only triggers when a TCP connection closes with incomplete frame data sitting in the ByteToMessageDecoder's cumulation buffer. Complete messages are fully consumed and released — so sending well-formed syslog messages will never reproduce the leak. Here's a test script: syslog_leak_test.sh |
|
Closing in favor of #25120 |


Usually the netty-component calling a
retain()on aByteBufis also responsible forrelease()ing it. Until now, there is aretain()in theSyslogTCPFramingRouterHandler, but we onlyrelease()it in case of an exception. This is on purpose, as when it comes to multi-frame messages, the first part(s) of the message were already released and thus not accessible, when the decoder is trying to do its job.Now, we keep track of all messages we
retain()and release them once the channel (usually network-connection) is closed or an exception occurs. That way we can be sure to release all allocated buffers, while keeping the buffers around for long enough to also be able to decode them correctly.Things to verify:
SyslogTCPFramingRouterHandlera singleton (possible threading-issues on the collection ofretainedBuffers)?No, it isn't. Netty is using it as part of its event-loop, guaranteeing that there's no other thread accessing it at the same time. Also, it doesn't contain shared state (across multiple channels), so it should be ok to keep track of the retained messages this way.
Description
Motivation and Context
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Checklist: