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/threadutility.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/helpers/threadutility.h"
19
#if !defined(LOG4CXX)
20
  #define LOG4CXX 1
21
#endif
22
#include "log4cxx/private/log4cxx_private.h"
23
#include "log4cxx/helpers/loglog.h"
24
#include "log4cxx/helpers/transcoder.h"
25
26
#include <atomic>
27
#include <signal.h>
28
#include <mutex>
29
#include <list>
30
#include <condition_variable>
31
#include <algorithm>
32
33
#ifdef _WIN32
34
  #include <windows.h>
35
  #include <processthreadsapi.h>
36
#endif
37
38
#if LOG4CXX_EVENTS_AT_EXIT
39
#include <log4cxx/private/atexitregistry.h>
40
#endif
41
#if !defined(LOG4CXX)
42
  #define LOG4CXX 1
43
#endif
44
#include <log4cxx/helpers/aprinitializer.h>
45
46
namespace LOG4CXX_NS
47
{
48
namespace helpers
49
{
50
51
struct ThreadUtility::priv_data
52
{
53
  priv_data()
54
#if LOG4CXX_EVENTS_AT_EXIT
55
    : atExitRegistryRaii{ [this]{ stopThread(); } }
56
#endif
57
0
  {
58
0
  }
59
60
  ~priv_data()
61
0
  { stopThread(); }
62
63
  ThreadStartPre  start_pre{nullptr};
64
  ThreadStarted   started{nullptr};
65
  ThreadStartPost start_post{nullptr};
66
67
  using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
68
  struct NamedPeriodicFunction
69
  {
70
    LogString             name;
71
    Period                delay;
72
    TimePoint             nextRun;
73
    std::function<void()> f;
74
    int                   errorCount;
75
    bool                  removed;
76
  };
77
  using JobStore = std::list<NamedPeriodicFunction>;
78
  JobStore                  jobs;
79
  std::recursive_mutex      job_mutex;
80
  std::thread               thread;
81
  std::condition_variable   interrupt;
82
  std::mutex                interrupt_mutex;
83
  bool                      wakeup{ false };
84
  std::atomic<bool>         terminated{ false };
85
  int                       retryCount{ 2 };
86
  Period                    maxDelay{ 0 };
87
  std::atomic<bool>         threadIsActive{ false };
88
  LoggerPtr                 log;
89
90
  void doPeriodicTasks();
91
92
  bool findRunnableTask(NamedPeriodicFunction *foundTask);
93
94
  void scheduleNextRun(const LogString& name, const Period& delay, bool success);
95
96
  void setTerminated()
97
0
  {
98
0
    std::lock_guard<std::recursive_mutex> lock(job_mutex);
99
0
    terminated.store(true);
100
0
  }
101
  
102
  void wakeThread()
103
0
  {
104
0
    std::unique_lock<std::mutex> lock(this->interrupt_mutex);
105
0
    this->wakeup = true;
106
0
    this->interrupt.notify_all();
107
0
  }
108
109
  void stopThread()
110
0
  {
111
0
    LOGLOG_DEBUG(log, "stopThread");
112
0
    setTerminated();
113
0
    wakeThread();
114
0
    if (thread.joinable())
115
0
      thread.join();
116
0
  }
117
118
#if LOG4CXX_EVENTS_AT_EXIT
119
  helpers::AtExitRegistry::Raii atExitRegistryRaii;
120
#endif
121
};
122
123
#if LOG4CXX_HAS_PTHREAD_SIGMASK
124
  static thread_local sigset_t old_mask;
125
  static thread_local bool sigmask_valid;
126
#endif
127
128
ThreadUtility::ThreadUtility()
129
0
  : m_priv( std::make_unique<priv_data>() )
130
0
{
131
  // Block signals by default.
132
0
  configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, this ),
133
0
    nullptr,
134
0
    std::bind( &ThreadUtility::postThreadUnblockSignals, this ) );
135
0
}
136
137
0
ThreadUtility::~ThreadUtility() {}
138
139
auto ThreadUtility::instancePtr() -> ManagerPtr
140
0
{
141
0
  auto result = APRInitializer::getOrAddUnique<Manager>
142
0
    ( []() -> ObjectPtr
143
0
      { return std::make_shared<Manager>(); }
144
0
    );
145
0
  return result;
146
0
}
147
148
ThreadUtility* ThreadUtility::instance()
149
0
{
150
0
  return &instancePtr()->value();
151
0
}
152
153
void ThreadUtility::configure( ThreadConfigurationType type )
154
0
{
155
0
  auto utility = instance();
156
157
0
  if ( type == ThreadConfigurationType::NoConfiguration )
158
0
  {
159
0
    utility->configureFuncs( nullptr, nullptr, nullptr );
160
0
  }
161
0
  else if ( type == ThreadConfigurationType::NameThreadOnly )
162
0
  {
163
0
    utility->configureFuncs( nullptr,
164
0
      std::bind( &ThreadUtility::threadStartedNameThread, utility,
165
0
        std::placeholders::_1,
166
0
        std::placeholders::_2,
167
0
        std::placeholders::_3 ),
168
0
      nullptr );
169
0
  }
170
0
  else if ( type == ThreadConfigurationType::BlockSignalsOnly )
171
0
  {
172
0
    utility->configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, utility ),
173
0
      nullptr,
174
0
      std::bind( &ThreadUtility::postThreadUnblockSignals, utility ) );
175
0
  }
176
0
  else if ( type == ThreadConfigurationType::BlockSignalsAndNameThread )
177
0
  {
178
0
    utility->configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, utility ),
179
0
      std::bind( &ThreadUtility::threadStartedNameThread, utility,
180
0
        std::placeholders::_1,
181
0
        std::placeholders::_2,
182
0
        std::placeholders::_3 ),
183
0
      std::bind( &ThreadUtility::postThreadUnblockSignals, utility ) );
184
0
  }
185
0
}
186
187
void ThreadUtility::configureFuncs( ThreadStartPre pre_start,
188
  ThreadStarted started,
189
  ThreadStartPost post_start )
190
0
{
191
0
  m_priv->start_pre = pre_start;
192
0
  m_priv->started = started;
193
0
  m_priv->start_post = post_start;
194
0
}
195
196
void ThreadUtility::preThreadBlockSignals()
197
0
{
198
0
#if LOG4CXX_HAS_PTHREAD_SIGMASK
199
0
  sigset_t set;
200
0
  sigfillset(&set);
201
202
0
  if ( pthread_sigmask(SIG_SETMASK, &set, &old_mask) < 0 )
203
0
  {
204
0
    LOGLOG_ERROR( LOG4CXX_STR("Unable to set thread sigmask") );
205
0
    sigmask_valid = false;
206
0
  }
207
0
  else
208
0
  {
209
0
    sigmask_valid = true;
210
0
  }
211
212
0
#endif /* LOG4CXX_HAS_PTHREAD_SIGMASK */
213
0
}
214
215
void ThreadUtility::threadStartedNameThread(LogString threadName,
216
  std::thread::id /*threadId*/,
217
  std::thread::native_handle_type nativeHandle)
218
0
{
219
0
#if LOG4CXX_HAS_PTHREAD_SETNAME && !(defined(_WIN32) && defined(_LIBCPP_VERSION))
220
0
  LOG4CXX_ENCODE_CHAR(sthreadName, threadName);
221
0
  if (pthread_setname_np(static_cast<pthread_t>(nativeHandle), sthreadName.c_str()) < 0) {
222
0
    LOGLOG_ERROR(LOG4CXX_STR("unable to set thread name"));
223
0
  }
224
#elif defined(_WIN32)
225
  typedef HRESULT (WINAPI *TSetThreadDescription)(HANDLE, PCWSTR);
226
  static struct initialiser
227
  {
228
    HMODULE hKernelBase;
229
    TSetThreadDescription SetThreadDescription;
230
    initialiser()
231
      : hKernelBase(GetModuleHandleA("KernelBase.dll"))
232
      , SetThreadDescription(nullptr)
233
    {
234
      if (hKernelBase)
235
        SetThreadDescription = reinterpret_cast<TSetThreadDescription>(GetProcAddress(hKernelBase, "SetThreadDescription"));
236
    }
237
  } win32Func;
238
  if (win32Func.SetThreadDescription)
239
  {
240
    LOG4CXX_ENCODE_WCHAR(wthreadName, threadName);
241
    if(FAILED(win32Func.SetThreadDescription(static_cast<HANDLE>(nativeHandle), wthreadName.c_str())))
242
      LOGLOG_ERROR( LOG4CXX_STR("unable to set thread name") );
243
  }
244
#endif
245
0
}
246
247
void ThreadUtility::postThreadUnblockSignals()
248
0
{
249
0
#if LOG4CXX_HAS_PTHREAD_SIGMASK
250
251
  // Only restore the signal mask if we were able to set it in the first place.
252
0
  if ( sigmask_valid )
253
0
  {
254
0
    if ( pthread_sigmask(SIG_SETMASK, &old_mask, nullptr) < 0 )
255
0
    {
256
0
      LOGLOG_ERROR( LOG4CXX_STR("Unable to set thread sigmask") );
257
0
    }
258
0
  }
259
260
0
#endif /* LOG4CXX_HAS_PTHREAD_SIGMASK */
261
0
}
262
263
264
ThreadStartPre ThreadUtility::preStartFunction()
265
0
{
266
0
  return m_priv->start_pre;
267
0
}
268
269
ThreadStarted ThreadUtility::threadStartedFunction()
270
0
{
271
0
  return m_priv->started;
272
0
}
273
274
ThreadStartPost ThreadUtility::postStartFunction()
275
0
{
276
0
  return m_priv->start_post;
277
0
}
278
279
/**
280
 * Add a periodic task
281
 */
282
void ThreadUtility::addPeriodicTask(const LogString& name, std::function<void()> f, const Period& delay)
283
0
{
284
0
  if (!m_priv->log)
285
0
    m_priv->log = LogLog::getLogger(LOG4CXX_STR("ThreadUtility"));
286
0
  LOGLOG_DEBUG(m_priv->log, LOG4CXX_STR("addPeriodicTask: ") << name);
287
0
  std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
288
0
  if (m_priv->maxDelay < delay)
289
0
    m_priv->maxDelay = delay;
290
0
  auto currentTime = std::chrono::system_clock::now();
291
0
  m_priv->jobs.push_back( priv_data::NamedPeriodicFunction{name, delay, currentTime + delay, f, 0, false} );
292
293
  // Restart thread if it has stopped.
294
0
  if (!m_priv->threadIsActive.load() && m_priv->thread.joinable())
295
0
    m_priv->thread.join();
296
297
0
  if (!m_priv->thread.joinable())
298
0
  {
299
0
    m_priv->terminated.store(false);
300
0
    m_priv->threadIsActive.store(true);
301
0
    m_priv->thread = createThread(LOG4CXX_STR("log4cxx"), [this]()
302
0
      {
303
0
        LOGLOG_DEBUG(m_priv->log, "doPeriodicTasks: " << "started");
304
0
        m_priv->doPeriodicTasks();
305
0
        LOGLOG_DEBUG(m_priv->log, "doPeriodicTasks: " << "stopped");
306
0
        m_priv->threadIsActive.store(false);
307
0
      });
308
0
  }
309
0
  else
310
0
    m_priv->wakeThread();
311
0
}
312
313
/**
314
 * Is this currently running a background thread?
315
 */
316
bool ThreadUtility::isProcessingThreadActive() const
317
0
{
318
0
  return m_priv->threadIsActive.load();
319
0
}
320
321
/**
322
 * Is this already running a \c taskName periodic task?
323
 */
324
bool ThreadUtility::hasPeriodicTask(const LogString& name)
325
0
{
326
0
  std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
327
0
  auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end()
328
0
    , [&name](const priv_data::NamedPeriodicFunction& item)
329
0
    { return !item.removed && name == item.name; }
330
0
    );
331
0
  return m_priv->jobs.end() != pItem;
332
0
}
333
334
/**
335
 * Remove all periodic tasks and stop the processing thread
336
 */
337
void ThreadUtility::removeAllPeriodicTasks()
338
0
{
339
0
  LOGLOG_DEBUG(m_priv->log, "removeAllPeriodicTasks");
340
0
  {
341
0
    std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
342
0
    while (!m_priv->jobs.empty())
343
0
      m_priv->jobs.pop_back();
344
0
  }
345
0
  m_priv->stopThread();
346
0
}
347
348
/**
349
 * Remove the \c taskName periodic task
350
 */
351
void ThreadUtility::removePeriodicTask(const LogString& name)
352
0
{
353
0
  std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
354
0
  auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end()
355
0
    , [&name](const priv_data::NamedPeriodicFunction& item)
356
0
    { return !item.removed && name == item.name; }
357
0
    );
358
0
  if (m_priv->jobs.end() != pItem)
359
0
  {
360
0
    LOGLOG_DEBUG(m_priv->log, LOG4CXX_STR("removePeriodicTask: ") << name);
361
0
    pItem->removed = true;
362
0
    m_priv->wakeThread();
363
0
  }
364
0
}
365
366
/**
367
 * Remove any periodic task matching \c namePrefix
368
 */
369
void ThreadUtility::removePeriodicTasksMatching(const LogString& namePrefix)
370
0
{
371
0
  while (1)
372
0
  {
373
0
    std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
374
0
    auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end()
375
0
      , [&namePrefix](const priv_data::NamedPeriodicFunction& item)
376
0
      { return !item.removed && namePrefix.size() <= item.name.size() && item.name.substr(0, namePrefix.size()) == namePrefix; }
377
0
      );
378
0
    if (m_priv->jobs.end() == pItem)
379
0
      break;
380
0
    pItem->removed = true;
381
0
  }
382
0
  m_priv->wakeThread();
383
0
}
384
385
// Run ready tasks
386
void ThreadUtility::priv_data::doPeriodicTasks()
387
0
{
388
0
  while (!this->terminated.load())
389
0
  {
390
0
    TimePoint nextOperationTime = std::chrono::system_clock::now() + this->maxDelay;
391
392
    // Run each due task with job_mutex released, so a long-running callback
393
    // (e.g. a reconnect blocked on network I/O) does not stall removePeriodicTask()
394
0
    while (!this->terminated.load())
395
0
    {
396
0
      NamedPeriodicFunction task;
397
0
      if (!this->findRunnableTask(&task)) // No tasks due?
398
0
        break;
399
400
      // Execute the callback outside any lock
401
0
      bool success = false;
402
0
      try
403
0
      {
404
0
        task.f();
405
0
        success = true;
406
0
      }
407
0
      catch (std::exception& ex)
408
0
      {
409
0
        LogLog::warn(task.name, ex);
410
0
      }
411
0
      catch (...)
412
0
      {
413
0
        LogLog::warn(task.name + LOG4CXX_STR(" threw an exception"));
414
0
      }
415
416
0
      this->scheduleNextRun(task.name, task.delay, success);
417
0
    }
418
419
    // Update nextOperationTime under the lock
420
0
    {
421
0
      std::lock_guard<std::recursive_mutex> lock(this->job_mutex);
422
0
      for (const auto& item : this->jobs)
423
0
      {
424
0
        if (!item.removed && item.nextRun < nextOperationTime)
425
0
        {
426
0
          nextOperationTime = item.nextRun;
427
0
        }
428
0
      }
429
0
    }
430
431
    // Delete removed and faulty tasks
432
0
    while (1)
433
0
    {
434
0
      std::lock_guard<std::recursive_mutex> lock(this->job_mutex);
435
0
      auto pItem = std::find_if(this->jobs.begin(), this->jobs.end()
436
0
        , [this](const NamedPeriodicFunction& item)
437
0
        { return item.removed || this->retryCount < item.errorCount; }
438
0
        );
439
0
      if (this->jobs.end() == pItem)
440
0
        break;
441
0
      LOGLOG_DEBUG(this->log, LOG4CXX_STR("doPeriodicTasks: erase ") << pItem->name);
442
0
      this->jobs.erase(pItem);
443
0
      if (this->jobs.empty())
444
0
        return;
445
0
    }
446
447
    // Wait until the next task is due or an add/remove/shutdown wakes us
448
0
    std::unique_lock<std::mutex> lock(this->interrupt_mutex);
449
0
    this->interrupt.wait_until(lock, nextOperationTime
450
0
      , [this]{ return this->wakeup; }
451
0
      );
452
0
    this->wakeup = false;
453
0
  }
454
0
}
455
456
bool ThreadUtility::priv_data::findRunnableTask(NamedPeriodicFunction *foundTask)
457
0
{
458
0
  std::lock_guard<std::recursive_mutex> lock(this->job_mutex);
459
0
  bool result = false;
460
0
  auto currentTime = std::chrono::system_clock::now();
461
0
  auto pItem = std::find_if(this->jobs.begin(), this->jobs.end()
462
0
    , [currentTime](const NamedPeriodicFunction& item)
463
0
    { return !item.removed && item.nextRun <= currentTime; }
464
0
    );
465
0
  if (pItem != this->jobs.end())
466
0
  {
467
0
    if (foundTask)
468
0
      *foundTask = *pItem;
469
0
    result = true;
470
0
  }
471
0
  return result;
472
0
}
473
474
void ThreadUtility::priv_data::scheduleNextRun(const LogString& name, const Period& delay, bool success)
475
0
{
476
0
  std::lock_guard<std::recursive_mutex> lock(this->job_mutex);
477
0
  auto pItem = std::find_if(this->jobs.begin(), this->jobs.end()
478
0
    , [&name](const NamedPeriodicFunction& item)
479
0
    { return !item.removed && name == item.name; }
480
0
    );
481
482
0
  if (pItem != this->jobs.end())
483
0
  {
484
    // Always push nextRun out, so a failing task waits before the next retry
485
0
    pItem->nextRun = std::chrono::system_clock::now() + delay;
486
0
    if (success)
487
0
      pItem->errorCount = 0;
488
0
    else
489
0
      ++pItem->errorCount;
490
0
  }
491
0
}
492
493
} //namespace helpers
494
} //namespace log4cxx