mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
210 lines
5.9 KiB
C++
210 lines
5.9 KiB
C++
/*! \file alarm.cpp
|
|
* \brief mux_alarm module.
|
|
*
|
|
* This module implements an Alarm Clock mechanism used to help abbreviate
|
|
* work as part of limiting CPU usage.
|
|
*/
|
|
|
|
#include "copyright.h"
|
|
#include "autoconf.h"
|
|
#include "config.h"
|
|
#include "core.h"
|
|
|
|
#include <limits>
|
|
|
|
mux_alarm alarm_clock;
|
|
|
|
/*! \brief Alarm Clock Thread Procedure.
|
|
*
|
|
* This thread waits on a condition variable. When set() is called, it waits
|
|
* with a timeout; if the timeout expires, alarmed is set. When clear() is
|
|
* called, the thread returns to waiting indefinitely.
|
|
*/
|
|
void mux_alarm::alarm_proc()
|
|
{
|
|
std::unique_lock<std::mutex> lock(mutex_);
|
|
for (;;)
|
|
{
|
|
// Wait until signaled or timeout expires.
|
|
//
|
|
wake_ = false;
|
|
if (alarm_period_.count() == 0)
|
|
{
|
|
// No alarm set -- wait indefinitely for a signal.
|
|
//
|
|
cv_.wait(lock, [this]{ return wake_; });
|
|
}
|
|
else
|
|
{
|
|
// Alarm is set -- wait with timeout.
|
|
//
|
|
if (!cv_.wait_for(lock, alarm_period_, [this]{ return wake_; }))
|
|
{
|
|
// Timed out -- fire the alarm.
|
|
//
|
|
alarmed.store(true);
|
|
alarm_period_ = std::chrono::milliseconds(0);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (shutdown_)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*! \brief Start the worker thread.
|
|
*
|
|
* Caller must hold mutex_. The new thread blocks on mutex_ until the caller
|
|
* releases it, then reads alarm_period_ directly -- so a wake_ raised before
|
|
* the thread existed is not "lost": a fresh worker does not need waking, it
|
|
* needs a period, and it reads the one already stored.
|
|
*/
|
|
void mux_alarm::start_thread_locked()
|
|
{
|
|
if (!thread_started_)
|
|
{
|
|
// Construct first, then mark started. If std::thread throws
|
|
// (resource exhaustion), leave thread_started_ false so a later
|
|
// set() can retry rather than silently never arming for the rest
|
|
// of the process lifetime.
|
|
//
|
|
alarm_thread_ = std::thread(&mux_alarm::alarm_proc, this);
|
|
thread_started_ = true;
|
|
}
|
|
}
|
|
|
|
/*! \brief Alarm Clock Constructor.
|
|
*
|
|
* Deliberately does NOT launch the alarm thread.
|
|
*
|
|
* alarm_clock is a namespace-scope global in libmux.so, so its constructor
|
|
* runs during static initialization -- while the dynamic loader is still
|
|
* working. Calling pthread_create from an ELF constructor is a known glibc
|
|
* hazard (thread startup needs TLS allocation, which contends with the
|
|
* loader), and it deadlocked this process before main in roughly 14% of runs
|
|
* (measured 7/50): both threads parked in futex_wait, zero output. That made
|
|
* `make test` hang nondeterministically on tests/netaddr/test_netaddr and hit
|
|
* any binary linking -lmux. LD_BIND_NOW=1 did not help, ruling out lazy
|
|
* binding.
|
|
*
|
|
* The thread is created on first set() instead -- by which time static
|
|
* initialization is long finished. A clock nobody has armed needs no worker:
|
|
* alarmed stays false, which is exactly right.
|
|
*/
|
|
mux_alarm::mux_alarm()
|
|
{
|
|
}
|
|
|
|
/*! \brief Alarm Clock Destructor.
|
|
*
|
|
* This function ensures the thread is completely shutdown and all resources
|
|
* are released.
|
|
*/
|
|
mux_alarm::~mux_alarm()
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
shutdown_ = true;
|
|
wake_ = true;
|
|
cv_.notify_one();
|
|
}
|
|
if (alarm_thread_.joinable())
|
|
{
|
|
alarm_thread_.join();
|
|
}
|
|
}
|
|
|
|
/*! \brief Sleep Routine.
|
|
*
|
|
* A sleep request does not prevent the Alarm Clock from firing, so typically,
|
|
* the server sleeps while the Alarm Clock is not set.
|
|
*/
|
|
void mux_alarm::sleep(CLinearTimeDelta sleep_period)
|
|
{
|
|
// Same int64_t / clamp path as set() — avoid long truncation (#1290).
|
|
using ms_rep = std::chrono::milliseconds::rep;
|
|
constexpr int64_t k100nsPerMs = 10000;
|
|
int64_t ms64 = sleep_period.Return100ns() / k100nsPerMs;
|
|
if (ms64 < 0)
|
|
{
|
|
ms64 = 0;
|
|
}
|
|
const auto ms_max = static_cast<int64_t>(
|
|
(std::numeric_limits<ms_rep>::max)());
|
|
if (ms64 > ms_max)
|
|
{
|
|
ms64 = ms_max;
|
|
}
|
|
std::this_thread::sleep_for(
|
|
std::chrono::milliseconds(static_cast<ms_rep>(ms64)));
|
|
}
|
|
|
|
/*! \brief Surrenders a little time.
|
|
*
|
|
* On most operating systems, a request to yield is a polite way of giving
|
|
* other threads the remainder of your time slice.
|
|
*/
|
|
void mux_alarm::surrender_slice()
|
|
{
|
|
std::this_thread::yield();
|
|
}
|
|
|
|
/*! \brief Set the Alarm Clock.
|
|
*
|
|
* This sets the Alarm Clock to fire after a certain time has passed.
|
|
*/
|
|
void mux_alarm::set(CLinearTimeDelta alarm_period)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
// ReturnMilliseconds() is long and can truncate on Win32 when the
|
|
// delta exceeds LONG_MAX ms (~24.8 days). Derive ms from 100ns ticks
|
|
// with int64_t and clamp to chrono::milliseconds' range (#1290).
|
|
//
|
|
using ms_rep = std::chrono::milliseconds::rep;
|
|
constexpr int64_t k100nsPerMs = 10000;
|
|
int64_t ms64 = alarm_period.Return100ns() / k100nsPerMs;
|
|
if (ms64 < 0)
|
|
{
|
|
ms64 = 0;
|
|
}
|
|
const auto ms_max = static_cast<int64_t>(
|
|
(std::numeric_limits<ms_rep>::max)());
|
|
if (ms64 > ms_max)
|
|
{
|
|
ms64 = ms_max;
|
|
}
|
|
alarm_period_ = std::chrono::milliseconds(static_cast<ms_rep>(ms64));
|
|
alarmed.store(false);
|
|
alarm_set_ = true;
|
|
wake_ = true;
|
|
|
|
// First arming creates the worker. Arming is the only operation that
|
|
// needs one, and by now we are well past static initialization.
|
|
//
|
|
start_thread_locked();
|
|
|
|
cv_.notify_one();
|
|
}
|
|
|
|
/*! \brief Clear the Alarm Clock.
|
|
*
|
|
* This turns the Alarm Clock off.
|
|
*/
|
|
void mux_alarm::clear()
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
alarm_period_ = std::chrono::milliseconds(0);
|
|
alarmed.store(false);
|
|
alarm_set_ = false;
|
|
wake_ = true;
|
|
cv_.notify_one();
|
|
}
|
|
|
|
bool mux_alarm::worker_started() const
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
return thread_started_;
|
|
}
|