MEDIUM: threads/freq_ctr: Make the frequency counters thread-safe

When a frequency counter must be updated, we use the curr_sec/curr_tick fields
as a lock, by setting the MSB to 1 in a compare-and-swap to lock and by reseting
it to unlock. And when we need to read it, we loop until the counter is
unlocked. This way, the frequency counters are thread-safe without any external
lock. It is important to avoid increasing the size of many structures (global,
proxy, server, stick_table).
This commit is contained in:
Christopher Faulet 2017-10-12 09:49:09 +02:00 committed by Willy Tarreau
parent b5997f740b
commit 94b712337d
2 changed files with 121 additions and 76 deletions

View File

@ -24,22 +24,9 @@
#include <common/config.h> #include <common/config.h>
#include <common/time.h> #include <common/time.h>
#include <common/hathreads.h>
#include <types/freq_ctr.h> #include <types/freq_ctr.h>
/* Rotate a frequency counter when current period is over. Must not be called
* during a valid period. It is important that it correctly initializes a null
* area.
*/
static inline void rotate_freq_ctr(struct freq_ctr *ctr)
{
ctr->prev_ctr = ctr->curr_ctr;
if (likely(now.tv_sec - ctr->curr_sec != 1)) {
/* we missed more than one second */
ctr->prev_ctr = 0;
}
ctr->curr_sec = now.tv_sec;
ctr->curr_ctr = 0; /* leave it at the end to help gcc optimize it away */
}
/* Update a frequency counter by <inc> incremental units. It is automatically /* Update a frequency counter by <inc> incremental units. It is automatically
* rotated if the period is over. It is important that it correctly initializes * rotated if the period is over. It is important that it correctly initializes
@ -47,32 +34,33 @@ static inline void rotate_freq_ctr(struct freq_ctr *ctr)
*/ */
static inline unsigned int update_freq_ctr(struct freq_ctr *ctr, unsigned int inc) static inline unsigned int update_freq_ctr(struct freq_ctr *ctr, unsigned int inc)
{ {
if (likely(ctr->curr_sec == now.tv_sec)) { unsigned int elapsed;
ctr->curr_ctr += inc; unsigned int tot_inc;
return ctr->curr_ctr; unsigned int curr_sec;
}
rotate_freq_ctr(ctr);
ctr->curr_ctr = inc;
return ctr->curr_ctr;
/* Note: later we may want to propagate the update to other counters */
}
/* Rotate a frequency counter when current period is over. Must not be called do {
* during a valid period. It is important that it correctly initializes a null /* remove the bit, used for the lock */
* area. This one works on frequency counters which have a period different curr_sec = ctr->curr_sec & 0x7fffffff;
* from one second.
*/
static inline void rotate_freq_ctr_period(struct freq_ctr_period *ctr,
unsigned int period)
{
ctr->prev_ctr = ctr->curr_ctr;
ctr->curr_tick += period;
if (likely(now_ms - ctr->curr_tick >= period)) {
/* we missed at least two periods */
ctr->prev_ctr = 0;
ctr->curr_tick = now_ms;
} }
ctr->curr_ctr = 0; /* leave it at the end to help gcc optimize it away */ while (!HA_ATOMIC_CAS(&ctr->curr_sec, &curr_sec, curr_sec | 0x8000000));
elapsed = (now.tv_sec & 0x7fffffff)- curr_sec;
if (unlikely(elapsed)) {
ctr->prev_ctr = ctr->curr_ctr;
ctr->curr_ctr = 0;
if (likely(elapsed != 1)) {
/* we missed more than one second */
ctr->prev_ctr = 0;
}
}
ctr->curr_ctr += inc;
tot_inc = ctr->curr_ctr;
/* release the lock and update the time in case of rotate. */
HA_ATOMIC_STORE(&ctr->curr_sec, now.tv_sec & 0x7fffffff);
return tot_inc;
/* Note: later we may want to propagate the update to other counters */
} }
/* Update a frequency counter by <inc> incremental units. It is automatically /* Update a frequency counter by <inc> incremental units. It is automatically
@ -83,13 +71,31 @@ static inline void rotate_freq_ctr_period(struct freq_ctr_period *ctr,
static inline unsigned int update_freq_ctr_period(struct freq_ctr_period *ctr, static inline unsigned int update_freq_ctr_period(struct freq_ctr_period *ctr,
unsigned int period, unsigned int inc) unsigned int period, unsigned int inc)
{ {
if (likely(now_ms - ctr->curr_tick < period)) { unsigned int tot_inc;
ctr->curr_ctr += inc; unsigned int curr_tick;
return ctr->curr_ctr;
do {
/* remove the bit, used for the lock */
curr_tick = (ctr->curr_tick >> 1) << 1;
} }
rotate_freq_ctr_period(ctr, period); while (!HA_ATOMIC_CAS(&ctr->curr_tick, &curr_tick, curr_tick | 0x1));
ctr->curr_ctr = inc;
return ctr->curr_ctr; if (now_ms - curr_tick >= period) {
ctr->prev_ctr = ctr->curr_ctr;
ctr->curr_ctr = 0;
curr_tick += period;
if (likely(now_ms - curr_tick >= period)) {
/* we missed at least two periods */
ctr->prev_ctr = 0;
curr_tick = now_ms;
}
}
ctr->curr_ctr += inc;
tot_inc = ctr->curr_ctr;
/* release the lock and update the time in case of rotate. */
HA_ATOMIC_STORE(&ctr->curr_tick, (curr_tick >> 1) << 1);
return tot_inc;
/* Note: later we may want to propagate the update to other counters */ /* Note: later we may want to propagate the update to other counters */
} }

View File

@ -31,17 +31,25 @@
unsigned int read_freq_ctr(struct freq_ctr *ctr) unsigned int read_freq_ctr(struct freq_ctr *ctr)
{ {
unsigned int curr, past; unsigned int curr, past;
unsigned int age; unsigned int age, curr_sec;
age = now.tv_sec - ctr->curr_sec; do {
curr = ctr->curr_ctr;
past = ctr->prev_ctr;
curr_sec = ctr->curr_sec;
} while (curr != ctr->curr_ctr
|| past != ctr->prev_ctr
|| curr_sec != ctr->curr_sec
|| (curr_sec & 0x80000000));
age = now.tv_sec - curr_sec;
if (unlikely(age > 1)) if (unlikely(age > 1))
return 0; return 0;
curr = 0; if (unlikely(age)) {
past = ctr->curr_ctr; past = curr;
if (likely(!age)) { curr = 0;
curr = past;
past = ctr->prev_ctr;
} }
if (past <= 1 && !curr) if (past <= 1 && !curr)
@ -57,16 +65,25 @@ unsigned int read_freq_ctr(struct freq_ctr *ctr)
unsigned int freq_ctr_remain(struct freq_ctr *ctr, unsigned int freq, unsigned int pend) unsigned int freq_ctr_remain(struct freq_ctr *ctr, unsigned int freq, unsigned int pend)
{ {
unsigned int curr, past; unsigned int curr, past;
unsigned int age; unsigned int age, curr_sec;
curr = 0; do {
age = now.tv_sec - ctr->curr_sec; curr = ctr->curr_ctr;
past = ctr->prev_ctr;
curr_sec = ctr->curr_sec;
if (likely(age <= 1)) { } while (curr != ctr->curr_ctr
past = ctr->curr_ctr; || past != ctr->prev_ctr
if (likely(!age)) { || curr_sec != ctr->curr_sec
curr = past; || (curr_sec & 0x80000000));
past = ctr->prev_ctr;
age = now.tv_sec - curr_sec;
if (unlikely(age > 1))
curr = 0;
else {
if (unlikely(age == 1)) {
past = curr;
curr = 0;
} }
curr += mul32hi(past, ms_left_scaled); curr += mul32hi(past, ms_left_scaled);
} }
@ -86,17 +103,25 @@ unsigned int freq_ctr_remain(struct freq_ctr *ctr, unsigned int freq, unsigned i
unsigned int next_event_delay(struct freq_ctr *ctr, unsigned int freq, unsigned int pend) unsigned int next_event_delay(struct freq_ctr *ctr, unsigned int freq, unsigned int pend)
{ {
unsigned int curr, past; unsigned int curr, past;
unsigned int wait, age; unsigned int wait, age, curr_sec;
past = 0; do {
curr = 0; curr = ctr->curr_ctr;
age = now.tv_sec - ctr->curr_sec; past = ctr->prev_ctr;
curr_sec = ctr->curr_sec;
if (likely(age <= 1)) { } while (curr != ctr->curr_ctr
past = ctr->curr_ctr; || past != ctr->prev_ctr
if (likely(!age)) { || curr_sec != ctr->curr_sec
curr = past; || (curr_sec & 0x80000000));
past = ctr->prev_ctr;
age = now.tv_sec - curr_sec;
if (unlikely(age > 1))
curr = 0;
else {
if (unlikely(age == 1)) {
past = curr;
curr = 0;
} }
curr += mul32hi(past, ms_left_scaled); curr += mul32hi(past, ms_left_scaled);
} }
@ -128,12 +153,19 @@ unsigned int next_event_delay(struct freq_ctr *ctr, unsigned int freq, unsigned
unsigned int read_freq_ctr_period(struct freq_ctr_period *ctr, unsigned int period) unsigned int read_freq_ctr_period(struct freq_ctr_period *ctr, unsigned int period)
{ {
unsigned int curr, past; unsigned int curr, past;
unsigned int remain; unsigned int remain, curr_tick;
curr = ctr->curr_ctr; do {
past = ctr->prev_ctr; curr = ctr->curr_ctr;
past = ctr->prev_ctr;
curr_tick = ctr->curr_tick;
remain = ctr->curr_tick + period - now_ms; } while (curr != ctr->curr_ctr
|| past != ctr->prev_ctr
|| curr_tick != ctr->curr_tick
|| (curr_tick & 0x1));
remain = curr_tick + period - now_ms;
if (unlikely((int)remain < 0)) { if (unlikely((int)remain < 0)) {
/* We're past the first period, check if we can still report a /* We're past the first period, check if we can still report a
* part of last period or if we're too far away. * part of last period or if we're too far away.
@ -159,12 +191,19 @@ unsigned int freq_ctr_remain_period(struct freq_ctr_period *ctr, unsigned int pe
unsigned int freq, unsigned int pend) unsigned int freq, unsigned int pend)
{ {
unsigned int curr, past; unsigned int curr, past;
unsigned int remain; unsigned int remain, curr_tick;
curr = ctr->curr_ctr; do {
past = ctr->prev_ctr; curr = ctr->curr_ctr;
past = ctr->prev_ctr;
curr_tick = ctr->curr_tick;
remain = ctr->curr_tick + period - now_ms; } while (curr != ctr->curr_ctr
|| past != ctr->prev_ctr
|| curr_tick != ctr->curr_tick
|| (curr_tick & 0x1));
remain = curr_tick + period - now_ms;
if (likely((int)remain < 0)) { if (likely((int)remain < 0)) {
/* We're past the first period, check if we can still report a /* We're past the first period, check if we can still report a
* part of last period or if we're too far away. * part of last period or if we're too far away.