/*
* Copyright (c) 2017 Richard Palethorpe <rpalethorpe@suse.com>
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Fuzzy Synchronisation - abreviated to fzsync
*
* This library is intended to help reproduce race conditions by providing two
* thread synchronisation mechanisms. The first is a 'checkpoint' system where
* each thread will wait indefinitely for the other to enter a checkpoint
* before continuing. This is acheived by calling tst_fzsync_wait() and/or
* tst_fzsync_wait_update() at the points you want to synchronise in each
* thread. This is functionally very similar to using pthread_barrier_wait()
* with two threads. However tst_fzsync_wait() can be inlined and is
* guaranteed not to call sleep or futex.
*
* The second takes the form a of a delay which is calculated by measuring the
* time difference between two points in each thread and comparing it to the
* desired difference (default is zero). Using a delay allows you to
* synchronise the threads at a point outside of your direct control
* (e.g. inside the kernel) or just increase the accuracy for the first
* mechanism. It is acheived using tst_fzsync_delay_{a,b}(),
* tst_fzsync_time_{a,b}() and tst_fzsync[_wait_]update().
*
* For a usage example see testcases/cve/cve-2016-7117.c or just run
* 'git grep tst_fuzzy_sync.h'
*/
#include <sys/time.h>
#include <time.h>
#include <math.h>
#include "tst_atomic.h"
#ifndef CLOCK_MONOTONIC_RAW
# define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC
#endif
/**
* struct tst_fzsync_pair - the state of a two way synchronisation
* @avg_diff: Internal; the average time difference over multiple iterations.
* @avg_diff_trgt: The desired average time difference, defaults to 0.
* @avg_alpha: The rate at which old diff samples are forgotten,
* defaults to 0.25.
* @avg_dev: Internal; Absolute average deviation.
* @a: Internal; The time at which call site A was last passed.
* @b: Internal; The time at which call site B was last passed.
* @delay: Internal; The size of the delay, positive to delay A,
* negative to delay B.
* @delay_inc: The step size of a delay increment, defaults to 1.
* @update_gap: The number of iterations between recalculating the delay.
* Defaults to 0xF and must be of the form $2^n - 1$
* @info_gap: The number of iterations between printing some statistics.
* Defaults to 0x7FFFF and must also be one less than a power of 2.
* @a_cntr: Internal; Atomic counter used by fzsync_pair_wait()
* @b_cntr: Internal; Atomic counter used by fzsync_pair_wait()
* @exit: Internal; Used by tst_fzsync_pair_exit() and fzsync_pair_wait()
*
* This contains all the necessary state for synchronising two points A and
* B. Where A is the time of an event in one process and B is the time of an
* event in another process.
*
* Internal fields should only be accessed by library functions.
*/
struct tst_fzsync_pair {
float avg_diff;
float avg_diff_trgt;
float avg_alpha;
float avg_dev;
struct timespec a;
struct timespec b;
long delay;
long delay_inc;
int update_gap;
int info_gap;
int a_cntr;
int b_cntr;
int exit;
};
/**
* TST_FZSYNC_PAIR_INIT - Default values for struct tst_fzysnc_pair
*/
#define TST_FZSYNC_PAIR_INIT { \
.avg_alpha = 0.25, \
.delay_inc = 1, \
.update_gap = 0xF, \
.info_gap = 0x7FFFF \
}
/**
* tst_fzsync_pair_info - Print some synchronisation statistics
*/
static void tst_fzsync_pair_info(struct tst_fzsync_pair *pair)
{
tst_res(TINFO,
"avg_diff = %.0fns, avg_dev = %.0fns, delay = %05ld loops",
pair->avg_diff, pair->avg_dev, pair->delay);
}
/**
* tst_fzsync_delay_a - Perform spin delay for A, if needed
*
* Usually called just before the point you want to synchronise.
*/
static inline void tst_fzsync_delay_a(struct tst_fzsync_pair *pair)
{
volatile long spin_delay = pair->delay;
while (spin_delay > 0)
spin_delay--;
}
/**
* tst_fzsync_delay_b - Perform spin delay for B, if needed
*
* Usually called just before the point you want to synchronise.
*/
static inline void tst_fzsync_delay_b(struct tst_fzsync_pair *pair)
{
volatile long spin_delay = pair->delay;
while (spin_delay < 0)
spin_delay++;
}
static inline void tst_fzsync_time(struct timespec *t)
{
clock_gettime(CLOCK_MONOTONIC_RAW, t);
}
/**
* tst_fzsync_time_a - Set A's time to now.
*
* Called at the point you want to synchronise.
*/
static inline void tst_fzsync_time_a(struct tst_fzsync_pair *pair)
{
tst_fzsync_time(&pair->a);
}
/**
* tst_fzsync_time_b - Set B's call time to now.
*
* Called at the point you want to synchronise.
*/
static inline void tst_fzsync_time_b(struct tst_fzsync_pair *pair)
{
tst_fzsync_time(&pair->b);
}
/**
* tst_exp_moving_avg - Exponential moving average
* @alpha: The preference for recent samples over old ones.
* @sample: The current sample
* @prev_avg: The average of the all the previous samples
*
* Returns average including the current sample.
*/
static inline float tst_exp_moving_avg(float alpha,
float sample,
float prev_avg)
{
return alpha * sample + (1.0 - alpha) * prev_avg;
}
/**
* tst_fzsync_pair_update - Recalculate the delay
* @loop_index: The i in "for(i = 0;..." or zero to ignore update_gap
* @pair: The state necessary for calculating the delay
*
* This should be called at the end of each loop to update the average
* measured time difference (between A and B) and update the delay. It is
* assumed that A and B are less than a second apart.
*
* The values of update_gap, avg_alpha and delay_inc decide the speed at which
* the algorithm approaches the optimum delay value and whether it is
* stable. If your test is behaving strangely, it could be because this
* algorithm is behaving chaotically and flip-flopping between large positve
* and negative delay values. You can call tst_fzysync_pair_info every few
* loops to check whether the average difference and delay values are stable.
*/
static void tst_fzsync_pair_update(int loop_index, struct tst_fzsync_pair *pair)
{
long diff;
long inc = pair->delay_inc;
float target = pair->avg_diff_trgt;
float avg = pair->avg_diff;
diff = pair->a.tv_nsec - pair->b.tv_nsec
+ 1000000000 * (pair->a.tv_sec - pair->b.tv_sec);
avg = tst_exp_moving_avg(pair->avg_alpha, diff, avg);
pair->avg_dev = tst_exp_moving_avg(pair->avg_alpha,
fabs(diff - avg),
pair->avg_dev);
if (!(loop_index & pair->update_gap)) {
if (avg > target)
pair->delay -= inc;
else if (avg < target)
pair->delay += inc;
}
if (!(loop_index & pair->info_gap))
tst_fzsync_pair_info(pair);
pair->avg_diff = avg;
}
/**
* tst_fzsync_pair_wait - Wait for the other thread
* @our_cntr: The counter for the thread we are on
* @other_cntr: The counter for the thread we are synchronising with
*
* Use this (through tst_fzsync_pair_wait_a() and tst_fzsync_pair_wait_b()) if
* you need an additional synchronisation point in a thread or you do not want
* to use the delay facility (not recommended). See
* tst_fzsync_pair_wait_update().
*
* Returns a non-zero value if the thread should continue otherwise the
* calling thread should exit.
*/
static inline int tst_fzsync_pair_wait(struct tst_fzsync_pair *pair,
int *our_cntr, int *other_cntr)
{
if (tst_atomic_inc(other_cntr) == INT_MAX) {
/*
* We are about to break the invariant that the thread with
* the lowest count is in front of the other. So we must wait
* here to ensure the other thread has atleast reached the
* line above before doing that. If we are in rear position
* then our counter may already have been set to zero.
*/
while (tst_atomic_load(our_cntr) > 0
&& tst_atomic_load(our_cntr) < INT_MAX
&& !tst_atomic_load(&pair->exit))
;
tst_atomic_store(0, other_cntr);
/*
* Once both counters have been set to zero the invariant
* is restored and we can continue.
*/
while (tst_atomic_load(our_cntr) > 1
&& !tst_atomic_load(&pair->exit))
;
} else {
/*
* If our counter is less than the other thread's we are ahead
* of it and need to wait.
*/
while (tst_atomic_load(our_cntr) < tst_atomic_load(other_cntr)
&& !tst_atomic_load(&pair->exit))
;
}
/* Only exit if we have done the same amount of work as the other thread */
return !tst_atomic_load(&pair->exit) ||
tst_atomic_load(other_cntr) <= tst_atomic_load(our_cntr);
}
static inline int tst_fzsync_wait_a(struct tst_fzsync_pair *pair)
{
return tst_fzsync_pair_wait(pair, &pair->a_cntr, &pair->b_cntr);
}
static inline int tst_fzsync_wait_b(struct tst_fzsync_pair *pair)
{
return tst_fzsync_pair_wait(pair, &pair->b_cntr, &pair->a_cntr);
}
/**
* tst_fzsync_pair_wait_update_{a,b} - Wait and then recalculate
*
* This allows you to have two long running threads which wait for each other
* every iteration. So each thread will exit this function at approximately
* the same time. It also updates the delay values in a thread safe manner.
*
* You must call this function in both threads the same number of times each
* iteration. So a call in one thread must match with a call in the
* other. Make sure that calls to tst_fzsync_pair_wait() and
* tst_fzsync_pair_wait_update() happen in the same order in each thread. That
* is, make sure that a call to tst_fzsync_pair_wait_update_a() in one thread
* corresponds to a call to tst_fzsync_pair_wait_update_b() in the other.
*
* Returns a non-zero value if the calling thread should continue to loop. If
* it returns zero then tst_fzsync_exit() has been called and you must exit
* the thread.
*/
static inline int tst_fzsync_wait_update_a(struct tst_fzsync_pair *pair)
{
static int loop_index;
tst_fzsync_pair_wait(pair, &pair->a_cntr, &pair->b_cntr);
loop_index++;
tst_fzsync_pair_update(loop_index, pair);
return tst_fzsync_pair_wait(pair, &pair->a_cntr, &pair->b_cntr);
}
static inline int tst_fzsync_wait_update_b(struct tst_fzsync_pair *pair)
{
tst_fzsync_pair_wait(pair, &pair->b_cntr, &pair->a_cntr);
return tst_fzsync_pair_wait(pair, &pair->b_cntr, &pair->a_cntr);
}
/**
* tst_fzsync_pair_exit - Signal that the other thread should exit
*
* Causes tst_fzsync_pair_wait() and tst_fzsync_pair_wait_update() to return
* 0.
*/
static inline void tst_fzsync_pair_exit(struct tst_fzsync_pair *pair)
{
tst_atomic_store(1, &pair->exit);
}