Coverage Report

Created: 2025-07-01 06:08

/src/logging-log4cxx/src/main/cpp/threadutility.cpp
Line
Count
Source (jump to first uncovered line)
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 <signal.h>
27
#include <mutex>
28
#include <list>
29
#include <condition_variable>
30
#include <algorithm>
31
32
#ifdef _WIN32
33
  #include <windows.h>
34
  #include <processthreadsapi.h>
35
#endif
36
37
#if LOG4CXX_EVENTS_AT_EXIT
38
#include <log4cxx/private/atexitregistry.h>
39
#endif
40
#if !defined(LOG4CXX)
41
  #define LOG4CXX 1
42
#endif
43
#include <log4cxx/helpers/aprinitializer.h>
44
45
namespace LOG4CXX_NS
46
{
47
namespace helpers
48
{
49
50
struct ThreadUtility::priv_data
51
{
52
  priv_data()
53
#if LOG4CXX_EVENTS_AT_EXIT
54
    : atExitRegistryRaii{ [this]{ stopThread(); } }
55
#endif
56
1
  {
57
1
  }
58
59
  ~priv_data()
60
1
  { stopThread(); }
61
62
  ThreadStartPre  start_pre{nullptr};
63
  ThreadStarted   started{nullptr};
64
  ThreadStartPost start_post{nullptr};
65
66
  using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
67
  struct NamedPeriodicFunction
68
  {
69
    LogString             name;
70
    Period                delay;
71
    TimePoint             nextRun;
72
    std::function<void()> f;
73
    int                   errorCount;
74
    bool                  removed;
75
  };
76
  using JobStore = std::list<NamedPeriodicFunction>;
77
  JobStore                  jobs;
78
  std::recursive_mutex      job_mutex;
79
  std::thread               thread;
80
  std::condition_variable   interrupt;
81
  std::mutex                interrupt_mutex;
82
  bool                      terminated{ false };
83
  int                       retryCount{ 2 };
84
  Period                    maxDelay{ 0 };
85
86
  void doPeriodicTasks();
87
88
  void setTerminated()
89
624
  {
90
624
    std::lock_guard<std::mutex> lock(interrupt_mutex);
91
624
    terminated = true;
92
624
  }
93
94
  void stopThread()
95
624
  {
96
624
    setTerminated();
97
624
    interrupt.notify_all();
98
624
    if (thread.joinable())
99
0
      thread.join();
100
624
  }
101
102
#if LOG4CXX_EVENTS_AT_EXIT
103
  helpers::AtExitRegistry::Raii atExitRegistryRaii;
104
#endif
105
};
106
107
#if LOG4CXX_HAS_PTHREAD_SIGMASK
108
  static thread_local sigset_t old_mask;
109
  static thread_local bool sigmask_valid;
110
#endif
111
112
ThreadUtility::ThreadUtility()
113
1
  : m_priv( std::make_unique<priv_data>() )
114
1
{
115
  // Block signals by default.
116
1
  configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, this ),
117
1
    nullptr,
118
1
    std::bind( &ThreadUtility::postThreadUnblockSignals, this ) );
119
1
}
120
121
1
ThreadUtility::~ThreadUtility() {}
122
123
auto ThreadUtility::instancePtr() -> ManagerPtr
124
623
{
125
623
  auto result = APRInitializer::getOrAddUnique<Manager>
126
623
    ( []() -> ObjectPtr
127
623
      { return std::make_shared<Manager>(); }
128
623
    );
129
623
  return result;
130
623
}
131
132
ThreadUtility* ThreadUtility::instance()
133
623
{
134
623
  return &instancePtr()->value();
135
623
}
136
137
void ThreadUtility::configure( ThreadConfigurationType type )
138
0
{
139
0
  auto utility = instance();
140
141
0
  if ( type == ThreadConfigurationType::NoConfiguration )
142
0
  {
143
0
    utility->configureFuncs( nullptr, nullptr, nullptr );
144
0
  }
145
0
  else if ( type == ThreadConfigurationType::NameThreadOnly )
146
0
  {
147
0
    utility->configureFuncs( nullptr,
148
0
      std::bind( &ThreadUtility::threadStartedNameThread, utility,
149
0
        std::placeholders::_1,
150
0
        std::placeholders::_2,
151
0
        std::placeholders::_3 ),
152
0
      nullptr );
153
0
  }
154
0
  else if ( type == ThreadConfigurationType::BlockSignalsOnly )
155
0
  {
156
0
    utility->configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, utility ),
157
0
      nullptr,
158
0
      std::bind( &ThreadUtility::postThreadUnblockSignals, utility ) );
159
0
  }
160
0
  else if ( type == ThreadConfigurationType::BlockSignalsAndNameThread )
161
0
  {
162
0
    utility->configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, utility ),
163
0
      std::bind( &ThreadUtility::threadStartedNameThread, utility,
164
0
        std::placeholders::_1,
165
0
        std::placeholders::_2,
166
0
        std::placeholders::_3 ),
167
0
      std::bind( &ThreadUtility::postThreadUnblockSignals, utility ) );
168
0
  }
169
0
}
170
171
void ThreadUtility::configureFuncs( ThreadStartPre pre_start,
172
  ThreadStarted started,
173
  ThreadStartPost post_start )
174
1
{
175
1
  m_priv->start_pre = pre_start;
176
1
  m_priv->started = started;
177
1
  m_priv->start_post = post_start;
178
1
}
log4cxx::helpers::ThreadUtility::configureFuncs(std::__1::function<void ()>, std::__1::function<void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::__thread_id, unsigned long)>, std::__1::function<void ()>)
Line
Count
Source
174
1
{
175
1
  m_priv->start_pre = pre_start;
176
1
  m_priv->started = started;
177
1
  m_priv->start_post = post_start;
178
1
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::configureFuncs(std::__1::function<void ()>, std::__1::function<void (std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> >, std::__1::__thread_id, unsigned long)>, std::__1::function<void ()>)
179
180
void ThreadUtility::preThreadBlockSignals()
181
0
{
182
0
#if LOG4CXX_HAS_PTHREAD_SIGMASK
183
0
  sigset_t set;
184
0
  sigfillset(&set);
185
186
0
  if ( pthread_sigmask(SIG_SETMASK, &set, &old_mask) < 0 )
187
0
  {
188
0
    LOGLOG_ERROR( LOG4CXX_STR("Unable to set thread sigmask") );
189
0
    sigmask_valid = false;
190
0
  }
191
0
  else
192
0
  {
193
0
    sigmask_valid = true;
194
0
  }
195
196
0
#endif /* LOG4CXX_HAS_PTHREAD_SIGMASK */
197
0
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::preThreadBlockSignals()
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::preThreadBlockSignals()
198
199
void ThreadUtility::threadStartedNameThread(LogString threadName,
200
  std::thread::id /*threadId*/,
201
  std::thread::native_handle_type nativeHandle)
202
0
{
203
0
#if LOG4CXX_HAS_PTHREAD_SETNAME && !(defined(_WIN32) && defined(_LIBCPP_VERSION))
204
0
  LOG4CXX_ENCODE_CHAR(sthreadName, threadName);
205
0
  if (pthread_setname_np(static_cast<pthread_t>(nativeHandle), sthreadName.c_str()) < 0) {
206
0
    LOGLOG_ERROR(LOG4CXX_STR("unable to set thread name"));
207
0
  }
208
#elif defined(_WIN32)
209
  typedef HRESULT (WINAPI *TSetThreadDescription)(HANDLE, PCWSTR);
210
  static struct initialiser
211
  {
212
    HMODULE hKernelBase;
213
    TSetThreadDescription SetThreadDescription;
214
    initialiser()
215
      : hKernelBase(GetModuleHandleA("KernelBase.dll"))
216
      , SetThreadDescription(nullptr)
217
    {
218
      if (hKernelBase)
219
        SetThreadDescription = reinterpret_cast<TSetThreadDescription>(GetProcAddress(hKernelBase, "SetThreadDescription"));
220
    }
221
  } win32Func;
222
  if (win32Func.SetThreadDescription)
223
  {
224
    LOG4CXX_ENCODE_WCHAR(wthreadName, threadName);
225
    if(FAILED(win32Func.SetThreadDescription(static_cast<HANDLE>(nativeHandle), wthreadName.c_str())))
226
      LOGLOG_ERROR( LOG4CXX_STR("unable to set thread name") );
227
  }
228
#endif
229
0
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::threadStartedNameThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::__thread_id, unsigned long)
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::threadStartedNameThread(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> >, std::__1::__thread_id, unsigned long)
230
231
void ThreadUtility::postThreadUnblockSignals()
232
0
{
233
0
#if LOG4CXX_HAS_PTHREAD_SIGMASK
234
235
  // Only restore the signal mask if we were able to set it in the first place.
236
0
  if ( sigmask_valid )
237
0
  {
238
0
    if ( pthread_sigmask(SIG_SETMASK, &old_mask, nullptr) < 0 )
239
0
    {
240
0
      LOGLOG_ERROR( LOG4CXX_STR("Unable to set thread sigmask") );
241
0
    }
242
0
  }
243
244
0
#endif /* LOG4CXX_HAS_PTHREAD_SIGMASK */
245
0
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::postThreadUnblockSignals()
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::postThreadUnblockSignals()
246
247
248
ThreadStartPre ThreadUtility::preStartFunction()
249
0
{
250
0
  return m_priv->start_pre;
251
0
}
252
253
ThreadStarted ThreadUtility::threadStartedFunction()
254
0
{
255
0
  return m_priv->started;
256
0
}
257
258
ThreadStartPost ThreadUtility::postStartFunction()
259
0
{
260
0
  return m_priv->start_post;
261
0
}
262
263
/**
264
 * Add a periodic task
265
 */
266
void ThreadUtility::addPeriodicTask(const LogString& name, std::function<void()> f, const Period& delay)
267
0
{
268
0
  std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
269
0
  if (m_priv->maxDelay < delay)
270
0
    m_priv->maxDelay = delay;
271
0
  auto currentTime = std::chrono::system_clock::now();
272
0
  m_priv->jobs.push_back( priv_data::NamedPeriodicFunction{name, delay, currentTime + delay, f, 0, false} );
273
0
  if (!m_priv->thread.joinable())
274
0
  {
275
0
    m_priv->terminated = false;
276
0
    m_priv->thread = createThread(LOG4CXX_STR("log4cxx"), std::bind(&priv_data::doPeriodicTasks, m_priv.get()));
277
0
  }
278
0
  else
279
0
    m_priv->interrupt.notify_one();
280
0
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::addPeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::function<void ()>, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> > const&)
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::addPeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&, std::__1::function<void ()>, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> > const&)
281
282
/**
283
 * Is this already running a \c taskName periodic task?
284
 */
285
bool ThreadUtility::hasPeriodicTask(const LogString& name)
286
0
{
287
0
  std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
288
0
  auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end()
289
0
    , [&name](const priv_data::NamedPeriodicFunction& item)
290
0
    { return !item.removed && name == item.name; }
Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const
Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const
291
0
    );
292
0
  return m_priv->jobs.end() != pItem;
293
0
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)
294
295
/**
296
 * Remove all periodic tasks and stop the processing thread
297
 */
298
void ThreadUtility::removeAllPeriodicTasks()
299
623
{
300
623
  {
301
623
    std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
302
623
    while (!m_priv->jobs.empty())
303
0
      m_priv->jobs.pop_back();
304
623
  }
305
623
  m_priv->stopThread();
306
623
}
307
308
/**
309
 * Remove the \c taskName periodic task
310
 */
311
void ThreadUtility::removePeriodicTask(const LogString& name)
312
0
{
313
0
  std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
314
0
  auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end()
315
0
    , [&name](const priv_data::NamedPeriodicFunction& item)
316
0
    { return !item.removed && name == item.name; }
Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const
Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const
317
0
    );
318
0
  if (m_priv->jobs.end() != pItem)
319
0
  {
320
0
    pItem->removed = true;
321
0
    m_priv->interrupt.notify_one();
322
0
  }
323
0
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)
324
325
/**
326
 * Remove any periodic task matching \c namePrefix
327
 */
328
void ThreadUtility::removePeriodicTasksMatching(const LogString& namePrefix)
329
0
{
330
0
  while (1)
331
0
  {
332
0
    std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex);
333
0
    auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end()
334
0
      , [&namePrefix](const priv_data::NamedPeriodicFunction& item)
335
0
      { return !item.removed && namePrefix.size() <= item.name.size() && item.name.substr(0, namePrefix.size()) == namePrefix; }
Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const
Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const
336
0
      );
337
0
    if (m_priv->jobs.end() == pItem)
338
0
      break;
339
0
    pItem->removed = true;
340
0
  }
341
0
  m_priv->interrupt.notify_one();
342
0
}
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)
343
344
// Run ready tasks
345
void ThreadUtility::priv_data::doPeriodicTasks()
346
0
{
347
0
  while (!this->terminated)
348
0
  {
349
0
    auto currentTime = std::chrono::system_clock::now();
350
0
    TimePoint nextOperationTime = currentTime + this->maxDelay;
351
0
    {
352
0
      std::lock_guard<std::recursive_mutex> lock(this->job_mutex);
353
0
      for (auto& item : this->jobs)
354
0
      {
355
0
        if (this->terminated)
356
0
          return;
357
0
        if (item.removed)
358
0
          ;
359
0
        else if (item.nextRun <= currentTime)
360
0
        {
361
0
          try
362
0
          {
363
0
            item.f();
364
0
            item.nextRun = std::chrono::system_clock::now() + item.delay;
365
0
            if (item.nextRun < nextOperationTime)
366
0
              nextOperationTime = item.nextRun;
367
0
            item.errorCount = 0;
368
0
          }
369
0
          catch (std::exception& ex)
370
0
          {
371
0
            LogLog::warn(item.name, ex);
372
0
            ++item.errorCount;
373
0
          }
374
0
          catch (...)
375
0
          {
376
0
            LogLog::warn(item.name + LOG4CXX_STR(" threw an exception"));
377
0
            ++item.errorCount;
378
0
          }
379
0
        }
380
0
        else if (item.nextRun < nextOperationTime)
381
0
          nextOperationTime = item.nextRun;
382
0
      }
383
0
    }
384
    // Delete removed and faulty tasks
385
0
    while (1)
386
0
    {
387
0
      std::lock_guard<std::recursive_mutex> lock(this->job_mutex);
388
0
      auto pItem = std::find_if(this->jobs.begin(), this->jobs.end()
389
0
        , [this](const NamedPeriodicFunction& item)
390
0
        { return item.removed || this->retryCount < item.errorCount; }
391
0
        );
392
0
      if (this->jobs.end() == pItem)
393
0
        break;
394
0
      this->jobs.erase(pItem);
395
0
      if (this->jobs.empty())
396
0
        return;
397
0
    }
398
399
0
    std::unique_lock<std::mutex> lock(this->interrupt_mutex);
400
0
    this->interrupt.wait_until(lock, nextOperationTime);
401
0
  }
402
0
}
403
404
} //namespace helpers
405
} //namespace log4cxx