291 lines
9.5 KiB
C
291 lines
9.5 KiB
C
/*
|
|
Copyright (c) 2013-2014 Anton Titov.
|
|
|
|
Copyright (c) 2013-2014 pCloud Ltd. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met: Redistributions of source code must retain the above
|
|
copyright notice, this list of conditions and the following
|
|
disclaimer. Redistributions in binary form must reproduce the
|
|
above copyright notice, this list of conditions and the following
|
|
disclaimer in the documentation and/or other materials provided
|
|
with the distribution. Neither the name of pCloud Ltd nor the
|
|
names of its contributors may be used to endorse or promote
|
|
products derived from this software without specific prior written
|
|
permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL pCloud
|
|
Ltd BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
|
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE.
|
|
*/
|
|
|
|
#include "pcache.h"
|
|
#include "pcompiler.h"
|
|
#include "plibs.h"
|
|
#include "prun.h"
|
|
#include "psys.h"
|
|
#include "ptimer.h"
|
|
|
|
/* Maximum timeout possible is TIMER_ARRAY_SIZE^TIMER_LEVELS seconds, in the
|
|
* worst case TIMER_LEVELS operations will be preformed for each timer to
|
|
* service it (it is log TIMER_ARRAY_SIZE(timer_seconds_after_now)). So
|
|
* servicing a timer is generally constant time task with a maximum constant of
|
|
* TIMER_LEVELS and increasing TIMER_ARRAY_SIZE will trade memory for less
|
|
* processing for each timer.
|
|
*
|
|
* TIMER_ARRAY_SIZE should be a power of two.
|
|
*/
|
|
|
|
#define TIMER_ARRAY_SIZE_SHIFT 6 /* 64 */
|
|
#define TIMER_ARRAY_SIZE (1 << TIMER_ARRAY_SIZE_SHIFT)
|
|
#define TIMER_LEVELS 3
|
|
|
|
#define PTIMER_IS_RUNNING 1
|
|
#define PTIMER_STOP_AFTER_RUN 2
|
|
|
|
time_t psync_current_time;
|
|
|
|
struct exception_list {
|
|
struct exception_list *next;
|
|
psync_exception_callback func;
|
|
pthread_t threadid;
|
|
};
|
|
|
|
static psync_list timerlists[TIMER_LEVELS][TIMER_ARRAY_SIZE];
|
|
static struct exception_list *excepions = NULL;
|
|
static struct exception_list *sleeplist = NULL;
|
|
static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_mutex_t timer_ex_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t timer_cond = PTHREAD_COND_INITIALIZER;
|
|
static uint32_t nextsecwaiters = 0;
|
|
static int timer_running = 0;
|
|
|
|
PSYNC_NOINLINE static void timer_sleep_detected(time_t lt) {
|
|
struct exception_list *e;
|
|
pdbg_logf(D_NOTICE, "sleep detected, current_time=%lu, last_current_time=%lu",
|
|
(unsigned long)__atomic_load_n(&psync_current_time, __ATOMIC_RELAXED), (unsigned long)lt);
|
|
pthread_mutex_lock(&timer_ex_mutex);
|
|
e = sleeplist;
|
|
while (e) {
|
|
e->func();
|
|
e = e->next;
|
|
}
|
|
pthread_mutex_unlock(&timer_ex_mutex);
|
|
pcache_clean();
|
|
ptimer_notify_exception();
|
|
}
|
|
|
|
static void timer_check_upper_levels(time_t tmdiv, unsigned long level,
|
|
unsigned long sh) {
|
|
psync_list *l1, *l2, *l;
|
|
time_t m;
|
|
m = tmdiv % TIMER_ARRAY_SIZE;
|
|
if (m == 0 && level < TIMER_LEVELS - 2)
|
|
timer_check_upper_levels(tmdiv / TIMER_ARRAY_SIZE, level + 1,
|
|
sh + TIMER_ARRAY_SIZE_SHIFT);
|
|
l = &timerlists[level + 1][m];
|
|
psync_list_for_each_safe(l1, l2, l) psync_list_add_tail(
|
|
&timerlists[level][(psync_list_element(l1, psync_timer_structure_t, list)
|
|
->runat >>
|
|
sh) %
|
|
TIMER_ARRAY_SIZE],
|
|
l1);
|
|
psync_list_init(&timerlists[level + 1][m]);
|
|
}
|
|
|
|
static void timer_prepare_timers(time_t from, time_t to, psync_list *list) {
|
|
time_t i, m;
|
|
psync_list *l1, *l2;
|
|
for (i = from + 1; i <= to; i++) {
|
|
m = i % TIMER_ARRAY_SIZE;
|
|
if (m == 0)
|
|
timer_check_upper_levels(i / TIMER_ARRAY_SIZE, 0, 0);
|
|
psync_list_for_each_safe(l1, l2, &timerlists[0][m]) {
|
|
psync_list_element(l1, psync_timer_structure_t, list)->opts |=
|
|
PTIMER_IS_RUNNING;
|
|
psync_list_add_tail(list, l1);
|
|
}
|
|
psync_list_init(&timerlists[0][m]);
|
|
}
|
|
}
|
|
|
|
PSYNC_NOINLINE static void timer_process_timers(psync_list *timers) {
|
|
psync_timer_t timer;
|
|
psync_list *l1, *l2;
|
|
psync_list_for_each_element(timer, timers, psync_timer_structure_t, list)
|
|
timer->call(timer, timer->param);
|
|
pthread_mutex_lock(&timer_mutex);
|
|
psync_list_for_each_safe(l1, l2, timers) {
|
|
timer = psync_list_element(l1, psync_timer_structure_t, list);
|
|
if (!(timer->opts & PTIMER_STOP_AFTER_RUN)) {
|
|
timer->opts = 0;
|
|
psync_list_del(l1);
|
|
timer->runat = __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED) + timer->numsec;
|
|
psync_list_add_tail(
|
|
&timerlists[timer->level][(timer->runat >>
|
|
(timer->level * TIMER_ARRAY_SIZE_SHIFT)) %
|
|
TIMER_ARRAY_SIZE],
|
|
&timer->list);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&timer_mutex);
|
|
psync_list_for_each_element_call(timers, psync_timer_structure_t, list, free);
|
|
}
|
|
|
|
static void timer_thread() {
|
|
psync_list timers;
|
|
time_t lt;
|
|
lt = __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED);
|
|
while (psync_do_run) {
|
|
psync_list_init(&timers);
|
|
psys_sleep_milliseconds(1000);
|
|
__atomic_store_n(&psync_current_time, psys_time_seconds(), __ATOMIC_RELAXED);
|
|
pthread_mutex_lock(&timer_mutex);
|
|
timer_prepare_timers(lt, __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED), &timers);
|
|
if (nextsecwaiters)
|
|
pthread_cond_broadcast(&timer_cond);
|
|
pthread_mutex_unlock(&timer_mutex);
|
|
if (unlikely(!psync_list_isempty(&timers)))
|
|
timer_process_timers(&timers);
|
|
if (unlikely(__atomic_load_n(&psync_current_time, __ATOMIC_RELAXED) - lt >= 25))
|
|
timer_sleep_detected(lt);
|
|
else if (pdbg_unlikely(__atomic_load_n(&psync_current_time, __ATOMIC_RELAXED) == lt)) {
|
|
if (!psync_do_run)
|
|
break;
|
|
psys_sleep_milliseconds(1000);
|
|
}
|
|
lt = __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED);
|
|
}
|
|
}
|
|
|
|
void ptimer_init() {
|
|
unsigned long i, j;
|
|
for (i = 0; i < TIMER_LEVELS; i++)
|
|
for (j = 0; j < TIMER_ARRAY_SIZE; j++)
|
|
psync_list_init(&timerlists[i][j]);
|
|
__atomic_store_n(&psync_current_time, psys_time_seconds(), __ATOMIC_RELAXED);
|
|
prun_thread("timer", timer_thread);
|
|
timer_running = 1;
|
|
}
|
|
|
|
time_t ptimer_time() {
|
|
if (timer_running)
|
|
return __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED);
|
|
else
|
|
return psys_time_seconds();
|
|
}
|
|
|
|
void ptimer_wake() { pthread_cond_signal(&timer_cond); }
|
|
|
|
psync_timer_t ptimer_register(psync_timer_callback func, time_t numsec,
|
|
void *param) {
|
|
psync_timer_t timer;
|
|
uint32_t i;
|
|
time_t n;
|
|
timer = malloc(sizeof(psync_timer_structure_t));
|
|
timer->call = func;
|
|
timer->param = param;
|
|
n = TIMER_ARRAY_SIZE;
|
|
for (i = 0; i < TIMER_LEVELS; i++) {
|
|
if (numsec <= n)
|
|
break;
|
|
else
|
|
n *= TIMER_ARRAY_SIZE;
|
|
}
|
|
if (unlikely(i == TIMER_LEVELS)) {
|
|
n /= TIMER_ARRAY_SIZE;
|
|
pdbg_logf(D_ERROR, "requested timeout %lu is larger than the maximum of %lu",
|
|
(unsigned long)numsec, (unsigned long)n);
|
|
numsec = n;
|
|
i--;
|
|
}
|
|
timer->numsec = numsec;
|
|
timer->level = i;
|
|
timer->opts = 0;
|
|
pthread_mutex_lock(&timer_mutex);
|
|
timer->runat = __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED) + numsec;
|
|
psync_list_add_tail(
|
|
&timerlists[i][(timer->runat >> (i * TIMER_ARRAY_SIZE_SHIFT)) %
|
|
TIMER_ARRAY_SIZE],
|
|
&timer->list);
|
|
pthread_mutex_unlock(&timer_mutex);
|
|
return timer;
|
|
}
|
|
|
|
int ptimer_stop(psync_timer_t timer) {
|
|
int needfree = 0;
|
|
pthread_mutex_lock(&timer_mutex);
|
|
if (timer->opts & PTIMER_IS_RUNNING)
|
|
timer->opts |= PTIMER_STOP_AFTER_RUN;
|
|
else {
|
|
psync_list_del(&timer->list);
|
|
needfree = 1;
|
|
}
|
|
pthread_mutex_unlock(&timer_mutex);
|
|
if (needfree) {
|
|
free(timer);
|
|
return 0;
|
|
} else
|
|
return 1;
|
|
}
|
|
|
|
void ptimer_exception_handler(psync_exception_callback func) {
|
|
struct exception_list *t;
|
|
t = malloc(sizeof(struct exception_list));
|
|
t->next = NULL;
|
|
t->func = func;
|
|
t->threadid = pthread_self();
|
|
pthread_mutex_lock(&timer_ex_mutex);
|
|
t->next = excepions;
|
|
excepions = t;
|
|
pthread_mutex_unlock(&timer_ex_mutex);
|
|
}
|
|
|
|
void ptimer_sleep_handler(psync_exception_callback func) {
|
|
struct exception_list *t;
|
|
t = malloc(sizeof(struct exception_list));
|
|
t->next = NULL;
|
|
t->func = func;
|
|
t->threadid = pthread_self();
|
|
pthread_mutex_lock(&timer_ex_mutex);
|
|
t->next = sleeplist;
|
|
sleeplist = t;
|
|
pthread_mutex_unlock(&timer_ex_mutex);
|
|
}
|
|
|
|
void ptimer_do_notify_exception() {
|
|
struct exception_list *e;
|
|
pthread_t threadid;
|
|
threadid = pthread_self();
|
|
pthread_mutex_lock(&timer_ex_mutex);
|
|
e = excepions;
|
|
while (e) {
|
|
if (!pthread_equal(threadid, e->threadid))
|
|
e->func();
|
|
e = e->next;
|
|
}
|
|
pthread_mutex_unlock(&timer_ex_mutex);
|
|
}
|
|
|
|
void ptimer_wait_next_sec() {
|
|
time_t ctime;
|
|
pthread_mutex_lock(&timer_mutex);
|
|
ctime = __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED);
|
|
do {
|
|
nextsecwaiters++;
|
|
pthread_cond_wait(&timer_cond, &timer_mutex);
|
|
nextsecwaiters--;
|
|
} while (ctime == __atomic_load_n(&psync_current_time, __ATOMIC_RELAXED));
|
|
pthread_mutex_unlock(&timer_mutex);
|
|
}
|