Coverage Report

Created: 2026-09-14 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/asyncappender.cpp
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
#include <log4cxx/asyncappender.h>
19
20
#include <log4cxx/helpers/loglog.h>
21
#include <log4cxx/spi/loggingevent.h>
22
#include <log4cxx/helpers/stringhelper.h>
23
#include <log4cxx/helpers/optionconverter.h>
24
#include <log4cxx/helpers/threadutility.h>
25
#include <log4cxx/private/appenderskeleton_priv.h>
26
#include <thread>
27
#include <atomic>
28
#include <condition_variable>
29
30
#if LOG4CXX_EVENTS_AT_EXIT
31
#include <log4cxx/private/atexitregistry.h>
32
#endif
33
34
using namespace LOG4CXX_NS;
35
using namespace LOG4CXX_NS::helpers;
36
using namespace LOG4CXX_NS::spi;
37
38
#if 15 < LOG4CXX_ABI_VERSION
39
namespace
40
{
41
#endif
42
43
/**
44
 * The default buffer size is set to 128 events.
45
*/
46
enum { DEFAULT_BUFFER_SIZE = 128 };
47
48
class DiscardSummary
49
{
50
  private:
51
    /**
52
     * First event of the highest severity.
53
    */
54
    LoggingEventPtr maxEvent;
55
56
    /**
57
    * Total count of messages discarded.
58
    */
59
    int count;
60
61
    /**
62
    * Why created
63
    */
64
    LogString reason;
65
66
  public:
67
    /**
68
     * Create new instance.
69
     *
70
     * @param event must not be null.
71
    */
72
    DiscardSummary(const LoggingEventPtr& event, const LogString& reason);
73
74
    /** Move values from \c src into a new instance.
75
    */
76
    DiscardSummary(DiscardSummary&& src);
77
#if 15 < LOG4CXX_ABI_VERSION
78
    /** Copy constructor.  */
79
    DiscardSummary(const DiscardSummary&) = delete;
80
    /** Assignment operator. */
81
    DiscardSummary& operator=(const DiscardSummary&) = delete;
82
#else
83
    /**
84
     * Create new instance.
85
     *
86
     * @param event event, may not be null.
87
    */
88
    DiscardSummary(const LoggingEventPtr& event);
89
    /** Copy constructor.  */
90
    DiscardSummary(const DiscardSummary& src);
91
    /** Assignment operator. */
92
    DiscardSummary& operator=(const DiscardSummary& src);
93
#endif
94
95
    /**
96
     * Add discarded event to summary.
97
     *
98
     * @param event event, may not be null.
99
    */
100
    void add(const LoggingEventPtr& event);
101
102
    /**
103
     * Create an event with a discard count and the message from \c maxEvent.
104
     *
105
     * @return the new event.
106
     */
107
    LoggingEventPtr createEvent();
108
109
#if LOG4CXX_ABI_VERSION <= 15
110
    LoggingEventPtr createEvent(Pool&);
111
    static
112
    ::LOG4CXX_NS::spi::LoggingEventPtr createEvent(::LOG4CXX_NS::helpers::Pool& p,
113
      size_t discardedCount);
114
#endif
115
116
    /**
117
    * The number of messages discarded.
118
    */
119
0
    int getCount() const { return count; }
120
};
121
122
typedef std::map<LogString, DiscardSummary> DiscardMap;
123
124
#if 15 < LOG4CXX_ABI_VERSION
125
}
126
#endif
127
128
#ifdef __cpp_lib_hardware_interference_size
129
  using std::hardware_constructive_interference_size;
130
  using std::hardware_destructive_interference_size;
131
#else
132
  // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ...
133
  constexpr std::size_t hardware_constructive_interference_size = 64;
134
  constexpr std::size_t hardware_destructive_interference_size = 64;
135
#endif
136
137
struct AsyncAppender::AsyncAppenderPriv : public AppenderSkeleton::AppenderSkeletonPrivate
138
{
139
  using BaseType = AppenderSkeleton::AppenderSkeletonPrivate;
140
  AsyncAppenderPriv()
141
0
    : AppenderSkeletonPrivate()
142
0
    , bufferSize(DEFAULT_BUFFER_SIZE)
143
0
    , blocking(true)
144
#if LOG4CXX_EVENTS_AT_EXIT
145
    , atExitRegistryRaii([this]{if (setClosed()) stopDispatcher();})
146
#endif
147
0
    , eventCount(0)
148
0
    , dispatchedCount(0)
149
0
    , commitCount(0)
150
0
    { }
151
152
  ~AsyncAppenderPriv()
153
0
  {
154
0
    if (setClosed())
155
0
      close();
156
0
  }
157
158
  /**
159
   * Event buffer.
160
  */
161
  struct EventData
162
  {
163
    LoggingEventPtr event;
164
    size_t pendingCount;
165
  };
166
  std::vector<EventData> buffer;
167
168
  /**
169
   *  Mutex used to guard access to buffer and discardMap.
170
   */
171
  std::mutex bufferMutex;
172
173
  std::condition_variable bufferNotFull;
174
  std::condition_variable bufferNotEmpty;
175
176
  /**
177
    * Map of DiscardSummary objects keyed by logger name.
178
  */
179
  DiscardMap discardMap;
180
181
  /**
182
   * The maximum number of undispatched events.
183
  */
184
  int bufferSize;
185
186
  /**
187
   * Nested appenders.
188
  */
189
  helpers::AppenderAttachableImpl appenders;
190
191
  /**
192
   *  Dispatcher.
193
   */
194
  std::thread dispatcher;
195
196
  /**
197
   * Serializes join()/joinable()/move-assignment on \c dispatcher:
198
   * concurrent use of those operations on the same std::thread object
199
   * is a data race with undefined behaviour.
200
   */
201
  std::mutex dispatcherMutex;
202
203
  /**
204
   * The dispatcher's thread id (written while holding \c dispatcherMutex,
205
   * readable by logging threads without touching the thread object).
206
   */
207
  std::atomic<std::thread::id> dispatcherId{ std::thread::id() };
208
209
  /**
210
   * Used to determine when to restart dispatch thread.
211
  */
212
  std::atomic<bool> dispatcherActive{ false };
213
214
  /**
215
   * Used to determine whether to restart dispatch thread.
216
  */
217
  int dispatcherStartCount{ 0 };
218
219
  /**
220
   *  The function the dispatcher executes.
221
   */
222
  void dispatch(const LogString& appenderName);
223
224
  /**
225
   * Start dispatcher if not already running.
226
   */
227
  void checkDispatcher(const LogString& appenderName)
228
0
  {
229
0
    if (this->dispatcherActive) // Fast path: no lock while the dispatcher is running
230
0
      return;
231
232
    // A stopped (or not yet started) dispatcher may be observed by several
233
    // logging threads concurrently; serialize all join()/joinable()/
234
    // move-assignment on the thread object.
235
0
    std::lock_guard<std::mutex> lock(this->dispatcherMutex);
236
237
    // Restart dispatcher if it has stopped by an exception in an attached appender.
238
0
    if (!this->dispatcherActive && this->dispatcher.joinable())
239
0
    {
240
0
      this->dispatcher.join();
241
0
      this->dispatcherId = std::thread::id();
242
0
    }
243
244
0
    if (!this->dispatcher.joinable() && this->dispatcherStartCount <= 1)
245
0
    {
246
0
      this->buffer.resize(this->bufferSize);
247
0
      ++this->dispatcherStartCount;
248
0
      this->dispatcherActive = true;
249
0
      this->dispatcher = ThreadUtility::instance()->createThread
250
0
        ( LOG4CXX_STR("AsyncAppender")
251
0
        , &AsyncAppender::AsyncAppenderPriv::dispatch
252
0
        , this
253
0
        , appenderName
254
0
        );
255
0
      this->dispatcherId = this->dispatcher.get_id();
256
0
    }
257
0
  }
258
259
  void stopDispatcher()
260
0
  {
261
0
    bufferNotEmpty.notify_all();
262
0
    bufferNotFull.notify_all();
263
264
    // Move the thread object out under the lock but join outside it:
265
    // the exiting dispatcher may still need bufferMutex, and a thread
266
    // blocked on dispatcherMutex may be holding bufferMutex.
267
0
    std::thread stoppedDispatcher;
268
0
    {
269
0
      std::lock_guard<std::mutex> lock(dispatcherMutex);
270
0
      stoppedDispatcher = std::move(dispatcher);
271
0
      dispatcherId = std::thread::id();
272
0
    }
273
0
    if (stoppedDispatcher.joinable())
274
0
    {
275
0
      stoppedDispatcher.join();
276
0
    }
277
0
  }
278
279
  void close();
280
281
  /**
282
   * Should location info be included in dispatched messages.
283
  */
284
  bool locationInfo{ true };
285
286
  /**
287
   * Does appender block when buffer is full.
288
  */
289
  bool blocking;
290
291
#if LOG4CXX_EVENTS_AT_EXIT
292
  helpers::AtExitRegistry::Raii atExitRegistryRaii;
293
#endif
294
295
  /**
296
   * Used to calculate the buffer position at which to store the next event.
297
  */
298
  alignas(hardware_constructive_interference_size) std::atomic<size_t> eventCount;
299
300
  /**
301
   * Used to calculate the buffer position from which to extract the next event.
302
  */
303
  alignas(hardware_constructive_interference_size) std::atomic<size_t> dispatchedCount;
304
305
  /**
306
   * Used to communicate to the dispatch thread when an event is committed in buffer.
307
  */
308
  alignas(hardware_constructive_interference_size) std::atomic<size_t> commitCount;
309
310
  bool isClosed()
311
0
  {
312
0
    std::lock_guard<std::mutex> lock(this->bufferMutex);
313
0
    return this->closed;
314
0
  }
315
316
  bool setClosed()
317
0
  {
318
0
    std::lock_guard<std::mutex> lock(this->bufferMutex);
319
0
    return BaseType::setClosed();
320
0
  }
321
322
  /**
323
   * Used to ensure the dispatch thread does not wait when a logging thread is waiting.
324
  */
325
  alignas(hardware_constructive_interference_size) int blockedCount{0};
326
};
327
328
329
IMPLEMENT_LOG4CXX_OBJECT(AsyncAppender)
330
331
0
#define priv static_cast<AsyncAppenderPriv*>(m_priv.get())
332
333
AsyncAppender::AsyncAppender()
334
0
  : AppenderSkeleton(std::make_unique<AsyncAppenderPriv>())
335
0
{
336
0
}
Unexecuted instantiation: log4cxx::AsyncAppender::AsyncAppender()
Unexecuted instantiation: log4cxx::AsyncAppender::AsyncAppender()
337
338
AsyncAppender::~AsyncAppender()
339
0
{
340
0
}
341
342
void AsyncAppender::addAppender(const AppenderPtr newAppender)
343
0
{
344
0
  priv->appenders.addAppender(newAppender);
345
0
}
346
347
348
void AsyncAppender::setOption(const LogString& option,
349
  const LogString& value)
350
0
{
351
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
352
0
  {
353
0
    setLocationInfo(OptionConverter::toBoolean(value, false));
354
0
  }
355
356
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
357
0
  {
358
0
    setBufferSize(OptionConverter::toInt(value, DEFAULT_BUFFER_SIZE));
359
0
  }
360
361
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BLOCKING"), LOG4CXX_STR("blocking")))
362
0
  {
363
0
    setBlocking(OptionConverter::toBoolean(value, true));
364
0
  }
365
0
  else
366
0
  {
367
0
    AppenderSkeleton::setOption(option, value);
368
0
  }
369
0
}
370
371
372
void AsyncAppender::doAppend( LOG4CXX_APPEND_FORMAL_PARAMETERS )
373
0
{
374
0
  doAppendImpl( LOG4CXX_APPEND_PARAMETERS );
375
0
}
376
377
void AsyncAppender::append( LOG4CXX_APPEND_FORMAL_PARAMETERS )
378
0
{
379
0
  if (priv->bufferSize <= 0)
380
0
  {
381
0
    priv->appenders.appendLoopOnAppenders(event);
382
0
    return;
383
0
  }
384
385
  // Get a copy of this thread's diagnostic context
386
0
  event->LoadDC();
387
388
0
  priv->checkDispatcher(getName());
389
390
0
  if (priv->dispatcherId.load() == std::this_thread::get_id()) // From an appender attached to this?
391
0
  {
392
0
    std::unique_lock<std::mutex> lock(priv->bufferMutex);
393
0
    auto loggerName = event->getLoggerName();
394
0
    auto iter = priv->discardMap.find(loggerName);
395
0
    if (priv->discardMap.end() == iter)
396
0
      priv->discardMap.emplace(loggerName, DiscardSummary{ event, LOG4CXX_STR("from an attached appender") });
397
0
    else
398
0
      iter->second.add(event);
399
0
  }
400
0
  else while (true)
401
0
  {
402
0
    auto pendingCount = priv->eventCount - priv->dispatchedCount;
403
0
    if (0 <= pendingCount && pendingCount < priv->buffer.size())
404
0
    {
405
      // Claim a slot in the ring buffer
406
0
      auto oldEventCount = priv->eventCount++;
407
0
      auto index = oldEventCount % priv->buffer.size();
408
      // Wait for a free slot
409
0
      while (priv->buffer.size() <= oldEventCount - priv->dispatchedCount)
410
0
        std::this_thread::yield(); // Allow the dispatch thread to free a slot
411
      // Write to the ring buffer
412
0
      priv->buffer[index] = AsyncAppenderPriv::EventData{event, pendingCount};
413
      // Notify the dispatch thread that an event has been added
414
0
      auto failureCount = 0;
415
0
      auto savedEventCount = oldEventCount;
416
0
      while (!priv->commitCount.compare_exchange_weak(oldEventCount, oldEventCount + 1, std::memory_order_release))
417
0
      {
418
0
        oldEventCount = savedEventCount;
419
0
        if (2 < ++failureCount) // Did the scheduler suspend a thread between claiming a slot and advancing commitCount?
420
0
          std::this_thread::yield(); // Wait a bit
421
0
      }
422
0
      priv->bufferNotEmpty.notify_all();
423
0
      break;
424
0
    }
425
    //
426
    //   Following code is only reachable if buffer is full or eventCount has overflowed
427
    //
428
0
    std::unique_lock<std::mutex> lock(priv->bufferMutex);
429
0
    priv->bufferNotEmpty.notify_all();
430
    //
431
    //   if blocking and thread is not already interrupted
432
    //      and not the dispatcher then
433
    //      wait for a buffer notification
434
0
    bool discard = true;
435
436
0
    if (priv->blocking
437
0
      && !priv->closed)
438
0
    {
439
0
      ++priv->blockedCount;
440
0
      priv->bufferNotFull.wait(lock, [this]()
441
0
      {
442
0
        priv->checkDispatcher(getName());
443
0
        return priv->eventCount - priv->dispatchedCount < priv->buffer.size();
444
0
      });
445
0
      --priv->blockedCount;
446
0
      discard = false;
447
0
    }
448
449
    //
450
    //   if blocking is false or thread has been interrupted
451
    //   add event to discard map.
452
    //
453
0
    if (discard)
454
0
    {
455
0
      LogString loggerName = event->getLoggerName();
456
0
      DiscardMap::iterator iter = priv->discardMap.find(loggerName);
457
458
0
      if (iter == priv->discardMap.end())
459
0
      {
460
0
        priv->discardMap.emplace(loggerName, DiscardSummary{ event, LOG4CXX_STR("due to a full event buffer") });
461
0
      }
462
0
      else
463
0
      {
464
0
        iter->second.add(event);
465
0
      }
466
467
0
      break;
468
0
    }
469
0
  }
470
0
}
471
472
void AsyncAppender::close()
473
0
{
474
0
  if (priv->setClosed())
475
0
    priv->close();
476
0
}
477
478
void AsyncAppender::AsyncAppenderPriv::close()
479
0
{
480
0
  this->stopDispatcher();
481
0
  for (auto item : this->appenders.getAllAppenders())
482
0
  {
483
0
    item->close();
484
0
  }
485
0
}
486
487
AppenderList AsyncAppender::getAllAppenders() const
488
0
{
489
0
  return priv->appenders.getAllAppenders();
490
0
}
491
492
AppenderPtr AsyncAppender::getAppender(const LogString& n) const
493
0
{
494
0
  return priv->appenders.getAppender(n);
495
0
}
496
497
bool AsyncAppender::isAttached(const AppenderPtr appender) const
498
0
{
499
0
  return priv->appenders.isAttached(appender);
500
0
}
501
502
bool AsyncAppender::requiresLayout() const
503
0
{
504
0
  return false;
505
0
}
506
507
void AsyncAppender::removeAllAppenders()
508
0
{
509
0
  priv->appenders.removeAllAppenders();
510
0
}
511
512
void AsyncAppender::removeAppender(const AppenderPtr appender)
513
0
{
514
0
  priv->appenders.removeAppender(appender);
515
0
}
516
517
void AsyncAppender::removeAppender(const LogString& n)
518
0
{
519
0
  priv->appenders.removeAppender(n);
520
0
}
521
522
bool AsyncAppender::replaceAppender(const AppenderPtr& oldAppender, const AppenderPtr& newAppender)
523
0
{
524
0
  return priv->appenders.replaceAppender(oldAppender, newAppender);
525
0
}
526
527
void AsyncAppender::replaceAppenders( const AppenderList& newList)
528
0
{
529
0
  priv->appenders.replaceAppenders(newList);
530
0
}
531
532
bool AsyncAppender::getLocationInfo() const
533
0
{
534
0
  return priv->locationInfo;
535
0
}
536
537
void AsyncAppender::setLocationInfo(bool flag)
538
0
{
539
0
  priv->locationInfo = flag;
540
0
}
541
542
void AsyncAppender::setBufferSize(int size)
543
0
{
544
0
  if (size < 0)
545
0
  {
546
0
    throw IllegalArgumentException(LOG4CXX_STR("size argument must be non-negative"));
547
0
  }
548
549
0
  std::lock_guard<std::mutex> lock(priv->dispatcherMutex);
550
0
  priv->bufferSize = (size < 1) ? 1 : size;
551
0
}
552
553
int AsyncAppender::getBufferSize() const
554
0
{
555
0
  std::lock_guard<std::mutex> lock(priv->dispatcherMutex);
556
0
  return priv->buffer.empty() ? priv->bufferSize : static_cast<int>(priv->buffer.size());
557
0
}
558
559
void AsyncAppender::setBlocking(bool value)
560
0
{
561
0
  std::lock_guard<std::mutex> lock(priv->bufferMutex);
562
0
  priv->blocking = value;
563
0
  priv->bufferNotFull.notify_all();
564
0
}
565
566
bool AsyncAppender::getBlocking() const
567
0
{
568
0
  std::lock_guard<std::mutex> lock(priv->bufferMutex);
569
0
  return priv->blocking;
570
0
}
571
572
DiscardSummary::DiscardSummary(const LoggingEventPtr& event, const LogString& reasonArg)
573
0
  : maxEvent(event)
574
0
  , count(1)
575
0
  , reason(reasonArg)
576
0
{
577
0
}
578
579
DiscardSummary::DiscardSummary(DiscardSummary&& other)
580
0
  : maxEvent(std::move(other.maxEvent))
581
0
  , count(other.count)
582
0
  , reason(std::move(other.reason))
583
0
{
584
0
}
585
586
#if LOG4CXX_ABI_VERSION <= 15
587
DiscardSummary::DiscardSummary(const LoggingEventPtr& event) :
588
0
  maxEvent(event), count(1)
589
0
{
590
0
}
591
592
DiscardSummary::DiscardSummary(const DiscardSummary& src) :
593
0
  maxEvent(src.maxEvent), count(src.count)
594
0
{
595
0
}
596
597
DiscardSummary& DiscardSummary::operator=(const DiscardSummary& src)
598
0
{
599
0
  maxEvent = src.maxEvent;
600
0
  count = src.count;
601
0
  return *this;
602
0
}
603
#endif
604
605
void DiscardSummary::add(const LoggingEventPtr& event)
606
0
{
607
0
  if (this->maxEvent->getLevel()->toInt() < event->getLevel()->toInt())
608
0
    this->maxEvent = event;
609
0
  ++this->count;
610
0
}
611
612
LoggingEventPtr DiscardSummary::createEvent()
613
0
{
614
0
  LogString msg(LOG4CXX_STR("Discarded "));
615
0
  StringHelper::toString(this->count, msg);
616
0
  msg.append(LOG4CXX_STR(" messages ") + this->reason + LOG4CXX_STR(" including: "));
617
0
  msg.append(this->maxEvent->getRenderedMessage());
618
0
  return std::make_shared<LoggingEvent>
619
0
    ( this->maxEvent->getLoggerName()
620
0
    , this->maxEvent->getLevel()
621
0
    , msg
622
0
    , LocationInfo::getLocationUnavailable()
623
0
    );
624
0
}
625
#if LOG4CXX_ABI_VERSION <= 15
626
LoggingEventPtr DiscardSummary::createEvent(Pool&)
627
0
{ return createEvent(); }
628
629
::LOG4CXX_NS::spi::LoggingEventPtr
630
DiscardSummary::createEvent(::LOG4CXX_NS::helpers::Pool& p,
631
  size_t discardedCount)
632
0
{
633
0
  LogString msg(LOG4CXX_STR("Discarded "));
634
0
  StringHelper::toString(discardedCount, msg);
635
0
  msg.append(LOG4CXX_STR(" messages due to a full event buffer"));
636
637
0
  return std::make_shared<LoggingEvent>(
638
0
        LOG4CXX_STR(""),
639
0
        LOG4CXX_NS::Level::getError(),
640
0
        msg,
641
0
        LocationInfo::getLocationUnavailable() );
642
0
}
643
#endif
644
645
void AsyncAppender::AsyncAppenderPriv::dispatch(const LogString& appenderName)
646
0
{
647
0
  size_t discardCount = 0;
648
0
  size_t iterationCount = 0;
649
0
  size_t waitCount = 0;
650
0
  size_t producerBlockedCount = 0;
651
0
  int failureCount = 0;
652
0
  std::vector<size_t> pendingCountHistogram(this->buffer.size(), 0);
653
0
  bool isActive = true;
654
655
0
  while (isActive)
656
0
  {
657
0
    LoggingEventList events;
658
0
    events.reserve(this->buffer.size());
659
0
    for (int count = 0; count < 2 && this->dispatchedCount == this->commitCount; ++count)
660
0
      std::this_thread::yield(); // Wait a bit
661
0
    if (this->dispatchedCount == this->commitCount)
662
0
    {
663
0
      ++waitCount;
664
0
      std::unique_lock<std::mutex> lock(this->bufferMutex);
665
0
      this->bufferNotEmpty.wait(lock, [this]() -> bool
666
0
        { return 0 < this->blockedCount || this->dispatchedCount != this->commitCount || this->closed; }
667
0
      );
668
0
    }
669
0
    isActive = !this->isClosed();
670
671
0
    while (events.size() < this->buffer.size() && this->dispatchedCount != this->commitCount)
672
0
    {
673
0
      auto index = this->dispatchedCount % this->buffer.size();
674
0
      const auto& data = this->buffer[index];
675
0
      events.push_back(data.event);
676
0
      if (data.pendingCount < pendingCountHistogram.size())
677
0
        ++pendingCountHistogram[data.pendingCount];
678
0
      ++this->dispatchedCount;
679
0
    }
680
0
    this->bufferNotFull.notify_all();
681
0
    {
682
0
      std::lock_guard<std::mutex> lock(this->bufferMutex);
683
0
      producerBlockedCount += this->blockedCount;
684
0
      for (auto& discardItem : this->discardMap)
685
0
      {
686
0
        events.push_back(discardItem.second.createEvent());
687
0
        discardCount += discardItem.second.getCount();
688
0
      }
689
0
      this->discardMap.clear();
690
0
    }
691
692
    // A fault in an attached appender must not permanently disable this
693
    // dispatch thread: producers using the default Blocking=true would
694
    // hang forever once the ring buffer fills. Reset the failure budget
695
    // for each batch so a transient fault (e.g. a temporarily full disk)
696
    // only limits retries within the current batch.
697
0
    failureCount = 0;
698
0
    for (auto item : events)
699
0
    {
700
0
      try
701
0
      {
702
0
        this->appenders.appendLoopOnAppenders(item);
703
0
      }
704
0
      catch (std::exception& ex)
705
0
      {
706
0
        if (1 < ++failureCount)
707
0
          break;
708
0
        if (!this->isClosed())
709
0
          this->errorHandler->error(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] AsyncAppender"), ex, spi::ErrorCode::WRITE_FAILURE, item);
710
0
      }
711
0
      catch (...)
712
0
      {
713
0
        if (1 < ++failureCount)
714
0
          break;
715
0
        if (!this->isClosed())
716
0
          this->errorHandler->error(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] AsyncAppender unknown exception thrown"));
717
0
      }
718
0
    }
719
0
    ++iterationCount;
720
0
  }
721
0
  if (LogLog::isDebugEnabled())
722
0
  {
723
0
    LogString msg(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] AsyncAppender"));
724
#ifdef _DEBUG
725
    msg += LOG4CXX_STR(" iterationCount ");
726
    StringHelper::toString(iterationCount, msg);
727
    msg += LOG4CXX_STR(" waitCount ");
728
    StringHelper::toString(waitCount, msg);
729
    msg += LOG4CXX_STR(" producerBlockedCount ");
730
    StringHelper::toString(producerBlockedCount, msg);
731
    msg += LOG4CXX_STR(" commitCount ");
732
    StringHelper::toString(this->commitCount, msg);
733
#endif
734
0
    msg += LOG4CXX_STR(" dispatchedCount ");
735
0
    StringHelper::toString(this->dispatchedCount, msg);
736
0
    msg += LOG4CXX_STR(" discardCount ");
737
0
    StringHelper::toString(discardCount, msg);
738
0
    msg += LOG4CXX_STR(" pendingCountHistogram");
739
0
    for (auto item : pendingCountHistogram)
740
0
    {
741
0
      msg += logchar(' ');
742
0
      StringHelper::toString(item, msg);
743
0
    }
744
0
    LogLog::debug(msg);
745
0
  }
746
0
  this->dispatcherActive = false;
747
0
  if (0 < this->blockedCount) // Restart this dispatcher?
748
0
    this->bufferNotFull.notify_all();
749
0
}