mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2026-02-11 12:22:09 +01:00
The freq counters were using the thread's own time as the start of the
current period. The problem is that in case of contention, it was
occasionally possible to perform non-monotonic updates on the edge of
the next second, because if the upfront thread updates a counter first,
it causes a rotation, then the second thread loses the race from its
older time, and tries again, and detects a different time again, but
in the past so it only updates the counter, then a third thread on the
new date would detect a change again, thus provoking a rotation again.
The effect was triple:
- rare loss of stored values during certain transitions from one
period to the next one, causing counters to report 0
- half of the threads forced to go through the slow path every second
- difficult convergence when using many threads where the CAS can fail
a lot and we can observe N(N-1) attempts for N threads to complete
This patch fixes this issue in two ways:
- first, it now makes use og the monotonic global_now value which also
happens to be volatile and to carry the latest known time; this way
time will never jump backwards anymore and only the first thread
updates it on transition, the other ones do not need to.
- second, re-read the time in the loop after each failure, because
if the date changed in the counter, it means that one thread knows
a more recent one and we need to update. In this case if it matches
the new current second, the fast path is usable.
This patch relies on previous patch "MINOR: time: export the global_now
variable" and must be backported as far as 1.8.
284 lines
8.0 KiB
C
284 lines
8.0 KiB
C
/*
|
|
* Event rate calculation functions.
|
|
*
|
|
* Copyright 2000-2010 Willy Tarreau <w@1wt.eu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/freq_ctr.h>
|
|
#include <haproxy/time.h>
|
|
#include <haproxy/tools.h>
|
|
|
|
/* Read a frequency counter taking history into account for missing time in
|
|
* current period. Current second is sub-divided in 1000 chunks of one ms,
|
|
* and the missing ones are read proportionally from previous value. The
|
|
* return value has the same precision as one input data sample, so low rates
|
|
* will be inaccurate still appropriate for max checking. One trick we use for
|
|
* low values is to specially handle the case where the rate is between 0 and 1
|
|
* in order to avoid flapping while waiting for the next event.
|
|
*
|
|
* For immediate limit checking, it's recommended to use freq_ctr_remain() and
|
|
* next_event_delay() instead which do not have the flapping correction, so
|
|
* that even frequencies as low as one event/period are properly handled.
|
|
*/
|
|
unsigned int read_freq_ctr(struct freq_ctr *ctr)
|
|
{
|
|
unsigned int curr, past, _curr, _past;
|
|
unsigned int age, curr_sec, _curr_sec;
|
|
|
|
while (1) {
|
|
_curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
_past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
_curr_sec = ctr->curr_sec;
|
|
__ha_compiler_barrier();
|
|
if (_curr_sec & 0x80000000)
|
|
continue;
|
|
curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
curr_sec = ctr->curr_sec;
|
|
__ha_compiler_barrier();
|
|
if (_curr == curr && _past == past && _curr_sec == curr_sec)
|
|
break;
|
|
}
|
|
|
|
age = (global_now >> 32) - curr_sec;
|
|
if (unlikely(age > 1))
|
|
return 0;
|
|
|
|
if (unlikely(age)) {
|
|
past = curr;
|
|
curr = 0;
|
|
}
|
|
|
|
if (past <= 1 && !curr)
|
|
return past; /* very low rate, avoid flapping */
|
|
|
|
return curr + mul32hi(past, ms_left_scaled);
|
|
}
|
|
|
|
/* returns the number of remaining events that can occur on this freq counter
|
|
* while respecting <freq> and taking into account that <pend> events are
|
|
* already known to be pending. Returns 0 if limit was reached.
|
|
*/
|
|
unsigned int freq_ctr_remain(struct freq_ctr *ctr, unsigned int freq, unsigned int pend)
|
|
{
|
|
unsigned int curr, past, _curr, _past;
|
|
unsigned int age, curr_sec, _curr_sec;
|
|
|
|
while (1) {
|
|
_curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
_past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
_curr_sec = ctr->curr_sec;
|
|
__ha_compiler_barrier();
|
|
if (_curr_sec & 0x80000000)
|
|
continue;
|
|
curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
curr_sec = ctr->curr_sec;
|
|
__ha_compiler_barrier();
|
|
if (_curr == curr && _past == past && _curr_sec == curr_sec)
|
|
break;
|
|
}
|
|
|
|
age = (global_now >> 32) - curr_sec;
|
|
if (unlikely(age > 1))
|
|
curr = 0;
|
|
else {
|
|
if (unlikely(age == 1)) {
|
|
past = curr;
|
|
curr = 0;
|
|
}
|
|
curr += mul32hi(past, ms_left_scaled);
|
|
}
|
|
curr += pend;
|
|
|
|
if (curr >= freq)
|
|
return 0;
|
|
return freq - curr;
|
|
}
|
|
|
|
/* return the expected wait time in ms before the next event may occur,
|
|
* respecting frequency <freq>, and assuming there may already be some pending
|
|
* events. It returns zero if we can proceed immediately, otherwise the wait
|
|
* time, which will be rounded down 1ms for better accuracy, with a minimum
|
|
* of one ms.
|
|
*/
|
|
unsigned int next_event_delay(struct freq_ctr *ctr, unsigned int freq, unsigned int pend)
|
|
{
|
|
unsigned int curr, past, _curr, _past;
|
|
unsigned int wait, age, curr_sec, _curr_sec;
|
|
|
|
while (1) {
|
|
_curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
_past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
_curr_sec = ctr->curr_sec;
|
|
__ha_compiler_barrier();
|
|
if (_curr_sec & 0x80000000)
|
|
continue;
|
|
curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
curr_sec = ctr->curr_sec;
|
|
__ha_compiler_barrier();
|
|
if (_curr == curr && _past == past && _curr_sec == curr_sec)
|
|
break;
|
|
}
|
|
|
|
age = (global_now >> 32) - curr_sec;
|
|
if (unlikely(age > 1))
|
|
curr = 0;
|
|
else {
|
|
if (unlikely(age == 1)) {
|
|
past = curr;
|
|
curr = 0;
|
|
}
|
|
curr += mul32hi(past, ms_left_scaled);
|
|
}
|
|
curr += pend;
|
|
|
|
if (curr < freq)
|
|
return 0;
|
|
|
|
/* too many events already, let's count how long to wait before they're
|
|
* processed. For this we'll subtract from the number of pending events
|
|
* the ones programmed for the current period, to know how long to wait
|
|
* for the next period. Each event takes 1/freq sec, thus 1000/freq ms.
|
|
*/
|
|
curr -= freq;
|
|
wait = curr * 1000 / (freq ? freq : 1);
|
|
return MAX(wait, 1);
|
|
}
|
|
|
|
/* Reads a frequency counter taking history into account for missing time in
|
|
* current period. The period has to be passed in number of ticks and must
|
|
* match the one used to feed the counter. The counter value is reported for
|
|
* current global date. The return value has the same precision as one input
|
|
* data sample, so low rates over the period will be inaccurate but still
|
|
* appropriate for max checking. One trick we use for low values is to specially
|
|
* handle the case where the rate is between 0 and 1 in order to avoid flapping
|
|
* while waiting for the next event.
|
|
*
|
|
* For immediate limit checking, it's recommended to use freq_ctr_period_remain()
|
|
* instead which does not have the flapping correction, so that even frequencies
|
|
* as low as one event/period are properly handled.
|
|
*
|
|
* For measures over a 1-second period, it's better to use the implicit functions
|
|
* above.
|
|
*/
|
|
unsigned int read_freq_ctr_period(struct freq_ctr_period *ctr, unsigned int period)
|
|
{
|
|
unsigned int _curr, _past, curr, past;
|
|
unsigned int remain, _curr_tick, curr_tick;
|
|
|
|
while (1) {
|
|
_curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
_past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
_curr_tick = ctr->curr_tick;
|
|
__ha_compiler_barrier();
|
|
if (_curr_tick & 0x1)
|
|
continue;
|
|
curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
curr_tick = ctr->curr_tick;
|
|
__ha_compiler_barrier();
|
|
if (_curr == curr && _past == past && _curr_tick == curr_tick)
|
|
break;
|
|
};
|
|
|
|
remain = curr_tick + period - (uint32_t)global_now / 1000;
|
|
if (unlikely((int)remain < 0)) {
|
|
/* We're past the first period, check if we can still report a
|
|
* part of last period or if we're too far away.
|
|
*/
|
|
remain += period;
|
|
if ((int)remain < 0)
|
|
return 0;
|
|
past = curr;
|
|
curr = 0;
|
|
}
|
|
if (past <= 1 && !curr)
|
|
return past; /* very low rate, avoid flapping */
|
|
|
|
curr += div64_32((unsigned long long)past * remain, period);
|
|
return curr;
|
|
}
|
|
|
|
/* Returns the number of remaining events that can occur on this freq counter
|
|
* while respecting <freq> events per period, and taking into account that
|
|
* <pend> events are already known to be pending. Returns 0 if limit was reached.
|
|
*/
|
|
unsigned int freq_ctr_remain_period(struct freq_ctr_period *ctr, unsigned int period,
|
|
unsigned int freq, unsigned int pend)
|
|
{
|
|
unsigned int _curr, _past, curr, past;
|
|
unsigned int remain, _curr_tick, curr_tick;
|
|
|
|
while (1) {
|
|
_curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
_past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
_curr_tick = ctr->curr_tick;
|
|
__ha_compiler_barrier();
|
|
if (_curr_tick & 0x1)
|
|
continue;
|
|
curr = ctr->curr_ctr;
|
|
__ha_compiler_barrier();
|
|
past = ctr->prev_ctr;
|
|
__ha_compiler_barrier();
|
|
curr_tick = ctr->curr_tick;
|
|
__ha_compiler_barrier();
|
|
if (_curr == curr && _past == past && _curr_tick == curr_tick)
|
|
break;
|
|
};
|
|
|
|
remain = curr_tick + period - (uint32_t)global_now / 1000;
|
|
if (likely((int)remain < 0)) {
|
|
/* We're past the first period, check if we can still report a
|
|
* part of last period or if we're too far away.
|
|
*/
|
|
past = curr;
|
|
curr = 0;
|
|
remain += period;
|
|
if ((int)remain < 0)
|
|
past = 0;
|
|
}
|
|
if (likely(past))
|
|
curr += div64_32((unsigned long long)past * remain, period);
|
|
|
|
curr += pend;
|
|
freq -= curr;
|
|
if ((int)freq < 0)
|
|
freq = 0;
|
|
return freq;
|
|
}
|
|
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|