You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@logging.apache.org by "OxBat (via GitHub)" <gi...@apache.org> on 2026/01/27 17:49:31 UTC
[PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
OxBat opened a new pull request, #586:
URL: https://github.com/apache/logging-log4cxx/pull/586
### Summary
I identified a critical algorithmic defect in `AsyncAppender::setBufferSize`.
The appender uses a Ring Buffer with modulo arithmetic (`index = counter % size`).
When the buffer size changes, the modulus changes, invalidating the mapping of existing data.
**The Crash:**
If the buffer contains wrapped data, the reader calculates indices based on the *new* size. These indices point to slots that are logically empty (containing `nullptr` from `std::vector::resize`). Dereferencing them causes a SEGFAULT.
### Technical Analysis
The `append` method uses a lock-free write path relying on `eventCount`.
`setBufferSize` simply called `resize()`, which breaks the `index = count % size` invariant for existing data.
### Remediation
The patch implements a "Drain and Re-align" strategy:
1. Creates a new aligned buffer.
2. Copies pending committed events linearly (starting at index 0).
3. Resets atomic counters (`dispatchedCount`, `commitCount`, `eventCount`) to match the new linear layout.
Note: This fix assumes the caller pauses logging before resizing (Quiescence), which is required anyway as `append()` does not hold the mutex during writes.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "OxBat (via GitHub)" <gi...@apache.org>.
OxBat commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3811465278
Thanks for the challenge @swebb2066.
The difference is fundamental: **The current implementation is mathematically broken even in a single-threaded (quiescent) scenario.**
Here is exactly why the current code causes a deterministic crash regardless of concurrency, based on `AsyncAppender::dispatch`:
**The Mathematical Flaw**
The reader logic at Line 519 relies on a strict invariant:
```cpp
// Line 519 in AsyncAppender::dispatch
auto index = priv->dispatchedCount % priv->buffer.size();
```
If `priv->buffer.size()` changes via `setBufferSize` (Line 448), the result of the modulus changes, but the data remains in the old slots. This invalidates the mapping immediately.
**Scenario (Current Code Failure):**
1. **Initial State:** `BufferSize` = 10. `dispatchedCount` = 15.
- The data is physically stored at index **5** (`15 % 10`).
2. **Action:** We call `setBufferSize(20)`.
- Line 448: `priv->buffer.resize(20)` expands the vector. The existing data stays at index 5. Indices 10-19 contain default-constructed/empty events.
3. **The Crash:** The dispatcher thread resumes at Line 519.
- It calculates: `15 % 20 = 15`.
- It accesses `buffer[15]`.
- **Result:** It reads an empty/invalid event instead of the real data at index 5. This leads to data corruption or a crash when dereferencing `event`.
**My Fix (Drain & Re-align)**
My patch fixes this by "linearizing" the buffer during resize. It moves the data from the old slots (e.g., index 5) to index 0, 1, 2... and resets the atomic counters to match the new linear layout (`dispatchedCount` becomes 0).
1. It moves data from slot **5** to slot **0**.
2. It resets `dispatchedCount` to **0**.
3. Line 519 becomes: `0 % 20 = 0`. **Success**.
**Conclusion:**
- **Current Code:** Broken by design. It fails 100% of the time if the buffer has wrapped, even if logging is paused.
- **My Fix:** Correct by design. It works reliably as long as the caller respects the quiescence contract.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "OxBat (via GitHub)" <gi...@apache.org>.
OxBat commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3817528460
Agreed, that's a fair point. Since the issue is just the modulo logic getting out of sync with the data layout, the bug is deterministic. We can reproduce it reliably without race conditions.
Here is a standalone test case. On master, this asserts or crashes because `close()` tries to drain from empty slots after the resize. With the fix, the data is realigned and everything passes.
You can add this to [src/test/cpp/asyncappendertestcase.cpp](https://github.com/apache/logging-log4cxx/blob/master/src/test/cpp/asyncappendertestcase.cpp):
```cpp
void testBufferResizeWraparound()
{
int initialSize = 10;
int newSize = 20;
auto asyncAppender = std::make_shared<AsyncAppender>();
asyncAppender->setBufferSize(initialSize);
auto vectorAppender = std::make_shared<VectorAppender>();
asyncAppender->addAppender(vectorAppender);
Pool p;
asyncAppender->activateOptions(p);
LoggerPtr root = Logger::getRootLogger();
// 1. warmup: push 5 events and let them drain completely.
// this moves the internal read head (dispatchedCount) to 5.
for (int i = 0; i < 5; ++i) {
auto event = std::make_shared<spi::LoggingEvent>(
root->getName(), Level::getInfo(), LOG4CXX_STR("Warmup"),
spi::LocationInfo::getLocationUnavailable());
asyncAppender->append(event, p);
}
// simple spin-wait to ensure dispatcher caught up
for (int i = 0; i < 100000 && vectorAppender->getVector().size() < 5; ++i) {
std::this_thread::yield();
}
LOGUNIT_ASSERT_EQUAL((size_t)5, vectorAppender->getVector().size());
// 2. wrap-around: fill the buffer (10 items).
// since we started at index 5, the data wraps: [10-14, 5-9]
for (int i = 0; i < 10; ++i) {
auto event = std::make_shared<spi::LoggingEvent>(
root->getName(), Level::getInfo(), LOG4CXX_STR("CrashTest"),
spi::LocationInfo::getLocationUnavailable());
asyncAppender->append(event, p);
}
// 3. resize while wrapped.
// without the fix, this leaves data at the old indices, but the read logic
// uses the new size modulo, pointing to empty slots.
asyncAppender->setBufferSize(newSize);
// 4. trigger drain.
// master: reads invalid memory/nullptr.
// fix: correctly reads realigned data.
asyncAppender->close();
LOGUNIT_ASSERT_EQUAL((size_t)15, vectorAppender->getVector().size());
}
```
I'm happy to include this test directly in the PR if that helps the review.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "rm5248 (via GitHub)" <gi...@apache.org>.
rm5248 commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3820743024
yes, please add this to the PR
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "swebb2066 (via GitHub)" <gi...@apache.org>.
swebb2066 closed pull request #586: Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer
URL: https://github.com/apache/logging-log4cxx/pull/586
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "rm5248 (via GitHub)" <gi...@apache.org>.
rm5248 commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3815169267
Are you able to create a test to validate this behavior? If it is reliable a test case to validate that it is broken or not would be helpful.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "swebb2066 (via GitHub)" <gi...@apache.org>.
swebb2066 commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3808785127
> Note: This fix assumes the caller pauses logging before resizing (Quiescence), which is required anyway as append() does not hold the mutex during writes.
So why is your fix is any better than the current implementation?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "swebb2066 (via GitHub)" <gi...@apache.org>.
swebb2066 commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3827759215
> as long as the caller respects the quiescence contract
I believe setBufferSize() could wait for dispatch thread quiescence by changing bufferSize to zero for the duration of the lock guarded changes (bufferSize would need to be an atomic variable)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "swebb2066 (via GitHub)" <gi...@apache.org>.
swebb2066 commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3838698463
> but this fixes the immediate segfault
I still observe a crash when running the new test repeatedly. I believe this is because the dispatch thread is not quiescent when the test calls setBufferSize(newSize).
I suggest the following simpler change to asyncappender.cpp. It does not require the dispatch thread is quiescent before setBufferSize() is called. The new test does not crash when run repeatedly.
```
diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index 8443d46f..faa86fb2 100644
--- a/src/main/cpp/asyncappender.cpp
+++ b/src/main/cpp/asyncappender.cpp
@@ -250,6 +250,11 @@ struct AsyncAppender::AsyncAppenderPriv : public AppenderSkeleton::AppenderSkele
* Used to ensure the dispatch thread does not wait when a logging thread is waiting.
*/
alignas(hardware_constructive_interference_size) int blockedCount{0};
+
+ /**
+ * Used to suspend use of the ring buffer.
+ */
+ alignas(hardware_constructive_interference_size) bool resizing{false};
};
@@ -304,7 +309,7 @@ void AsyncAppender::doAppend(const spi::LoggingEventPtr& event, Pool& pool1)
void AsyncAppender::append(const spi::LoggingEventPtr& event, Pool& p)
{
- if (priv->bufferSize <= 0)
+ if (priv->bufferSize <= 0 || priv->resizing)
{
priv->appenders.appendLoopOnAppenders(event, p);
return;
@@ -472,9 +477,16 @@ void AsyncAppender::setBufferSize(int size)
throw IllegalArgumentException(LOG4CXX_STR("size argument must be non-negative"));
}
+ priv->resizing = true;
+ while (priv->commitCount != priv->dispatchedCount)
+ {
+ priv->bufferNotEmpty.notify_all();
+ std::this_thread::yield(); // Wait a bit
+ }
std::lock_guard<std::mutex> lock(priv->bufferMutex);
priv->bufferSize = (size < 1) ? 1 : size;
priv->buffer.resize(priv->bufferSize);
+ priv->resizing = false;
priv->bufferNotFull.notify_all();
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "swebb2066 (via GitHub)" <gi...@apache.org>.
swebb2066 commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3839357783
The following improved change also prevents a race condition when events are added to AsyncAppender concurrently with a thread calling setBufferSize():
```
diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index 8443d46f..a0a7c1c3 100644
--- a/src/main/cpp/asyncappender.cpp
+++ b/src/main/cpp/asyncappender.cpp
@@ -250,6 +250,11 @@ struct AsyncAppender::AsyncAppenderPriv : public AppenderSkeleton::AppenderSkele
* Used to ensure the dispatch thread does not wait when a logging thread is waiting.
*/
alignas(hardware_constructive_interference_size) int blockedCount{0};
+
+ /**
+ * Used to ensure resizing does not proceed when a logging thread is adding to the buffer.
+ */
+ alignas(hardware_constructive_interference_size) std::atomic<size_t> activeCount;
};
@@ -304,9 +309,11 @@ void AsyncAppender::doAppend(const spi::LoggingEventPtr& event, Pool& pool1)
void AsyncAppender::append(const spi::LoggingEventPtr& event, Pool& p)
{
- if (priv->bufferSize <= 0)
+ auto activeCount = priv->activeCount.fetch_add(1, std::memory_order_acq_rel);
+ if (priv->bufferSize <= 0 || (activeCount & 0x100000000u))
{
priv->appenders.appendLoopOnAppenders(event, p);
+ priv->activeCount.fetch_sub(1, std::memory_order_release);
return;
}
@@ -328,6 +335,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& event, Pool& p)
priv->discardMap.emplace(loggerName, DiscardSummary{ event, LOG4CXX_STR("from an attached appender") });
else
iter->second.add(event);
+ priv->activeCount.fetch_sub(1, std::memory_order_release);
}
else while (true)
{
@@ -352,8 +360,10 @@ void AsyncAppender::append(const spi::LoggingEventPtr& event, Pool& p)
std::this_thread::yield(); // Wait a bit
}
priv->bufferNotEmpty.notify_all();
+ priv->activeCount.fetch_sub(1, std::memory_order_release);
break;
}
+ priv->activeCount.fetch_sub(1, std::memory_order_release);
//
// Following code is only reachable if buffer is full or eventCount has overflowed
//
@@ -397,6 +407,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& event, Pool& p)
break;
}
+ priv->activeCount.fetch_add(1, std::memory_order_acq_rel);
}
}
@@ -472,9 +483,17 @@ void AsyncAppender::setBufferSize(int size)
throw IllegalArgumentException(LOG4CXX_STR("size argument must be non-negative"));
}
+ auto activeCount = priv->activeCount.fetch_add(0x100000000u, std::memory_order_acq_rel) & 0xFFFFFFFFu;
+ while (activeCount || priv->commitCount != priv->dispatchedCount)
+ {
+ priv->bufferNotEmpty.notify_all();
+ std::this_thread::yield(); // Wait a bit
+ activeCount = priv->activeCount & 0xFFFFFFFFu;
+ }
std::lock_guard<std::mutex> lock(priv->bufferMutex);
priv->bufferSize = (size < 1) ? 1 : size;
priv->buffer.resize(priv->bufferSize);
+ priv->activeCount.fetch_sub(0x100000000u, std::memory_order_release);
priv->bufferNotFull.notify_all();
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "swebb2066 (via GitHub)" <gi...@apache.org>.
swebb2066 commented on code in PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#discussion_r2756932288
##########
src/test/cpp/asyncappendertestcase.cpp:
##########
@@ -739,6 +740,62 @@ class AsyncAppenderTestCase : public AppenderSkeletonTestCase
}
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
+ /**
+ * Tests that resizing the buffer when data is wrapped around (index > size)
+ * does not cause a crash or data loss.
+ */
+ void testBufferResizeWraparound()
+ {
+ int initialSize = 10;
+ int newSize = 20;
+
+ auto asyncAppender = std::make_shared<AsyncAppender>();
+ asyncAppender->setBufferSize(initialSize);
+
+ auto vectorAppender = std::make_shared<VectorAppender>();
Review Comment:
VectorAppender cannot be used as it is not thread safe. I Suggest using the following thread safe appender instead:
```
class CountingAppender : public AppenderSkeleton
{
public:
std::atomic<int> count { 0 };
void close() override
{}
void append(const spi::LoggingEventPtr& /*event*/, helpers::Pool& /*p*/) override
{
++count;
}
bool requiresLayout() const override
{
return false;
}
};
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Fix: Deterministic Crash (SIGSEGV) in AsyncAppender when resizing buffer [logging-log4cxx]
Posted by "OxBat (via GitHub)" <gi...@apache.org>.
OxBat commented on PR #586:
URL: https://github.com/apache/logging-log4cxx/pull/586#issuecomment-3834596643
@rm5248 Added `testBufferResizeWraparound` to the PR. It reproduces the crash on master and confirms the fix works.
@swebb2066 Valid point on quiescence. The problem is that even if we pause the thread perfectly, the logic is still broken: The reader uses `index % new_size`, but the data is sitting at `index % old_size`. So on resume, we read empty slots and crash immediately.
This PR fixes the data alignment so resizing is actually possible. We can look at better locking/atomics in a follow-up, but this fixes the immediate segfault
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@logging.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org