Implemented Deadline Timer and timer_test

This commit is contained in:
slvr 2013-08-12 13:08:03 +01:00
parent 139d915482
commit 1a39c397c5
6 changed files with 455 additions and 1 deletions

View File

@ -10,7 +10,8 @@ set(core_sources
LAN_discovery.c LAN_discovery.c
Messenger.c Messenger.c
util.c util.c
ping.c) ping.c
timer.c)
if(SHARED_TOXCORE) if(SHARED_TOXCORE)
add_library(toxcore SHARED ${core_sources}) add_library(toxcore SHARED ${core_sources})

273
core/timer.c Normal file
View File

@ -0,0 +1,273 @@
#include "timer.h"
#include "network.h"
#include <stdint.h>
/*
A nested linked list increases efficiency of insertions.
Depending on the number of timers we have, we might need to have nested linked lists
in order to improve insertion efficiency.
The code below is preperation for that end, should it be necessary.
typedef struct {
struct timer_package* _next;
union {
timer_packet* _inner;
timer* queue;
};
uint64_t pkgtime;
} timer_package;
timer_package* timer_package_pool;
static timer_package* new_package()
{
timer_package* ret;
if (timer_package_pool) {
ret = timer_package_pool;
timer_package_pool = timer_package_pool->_next;
} else {
ret = calloc(1, sizeof(struct timer_package));
}
return ret;
}
static void delete_package(timer_package* p)
{
p->_next = timer_package_pool;
timer_package_pool = p;
}
*/
enum timer_state {
STATE_INACTIVE = 0,
STATE_ACTIVE,
STATE_CALLBACK
};
struct timer
{
enum timer_state state;
timer* _prev;
timer* _next;
timer_callback cb;
void* userdata;
uint64_t deadline;
};
static timer* timer_main_queue;
static timer* timer_us_queue; /* hi-speed queue */
inline static void timer_dequeue(timer* t, timer** queue)
{
if (t->state == STATE_INACTIVE) return; /* not in a queue */
if (t->_prev) {
t->_prev->_next = t->_next;
} else {
*queue = t->_next;
}
if (t->_next) t->_next->_prev = t->_prev;
t->state = STATE_INACTIVE;
}
static void timer_enqueue(timer* t, timer** queue, timer* prev)
{
t->state = STATE_ACTIVE;
while (true) {
if (!*queue) {
t->_next = 0;
t->_prev = prev;
*queue = t;
return;
}
if ((*queue)->deadline > t->deadline) {
(*queue)->_prev = t;
t->_next = *queue;
t->_prev = prev;
*queue = t;
return;
}
prev = *queue;
queue = &((*queue)->_next);
}
}
/*** interface ***/
void timer_init()
{
/* Nothing needs to be done... yet. */
}
/* Do not depend on fields being zeroed */
static timer* timer_pool; /* timer_pool is SINGLY LINKED!! */
timer* timer_new(void)
{
timer* ret;
if (timer_pool) {
ret = timer_pool;
timer_pool = timer_pool->_next;
} else {
ret = calloc(1, sizeof(struct timer));
}
ret->state = STATE_INACTIVE;
return ret;
}
void timer_delete(timer* t)
{
timer_dequeue(t, &timer_main_queue);
t->_next = timer_pool;
t->state = STATE_INACTIVE;
timer_pool = t;
}
void timer_setup(timer* t, timer_callback cb, void* userarg)
{
t->cb = cb;
t->userdata = userarg;
}
void* timer_get_userdata(timer* t)
{
return t->userdata;
}
static void timer_delay_us(timer* t, int us)
{
t->deadline += us;
timer** queue = t->_prev ? &(t->_prev->_next) : &timer_main_queue;
timer_dequeue(t, &timer_main_queue);
timer_enqueue(t, queue, t->_prev);
}
/* Starts the timer so that it's called in sec seconds in the future.
* A non-positive value of sec results in the callback being called immediately.
* This function may be called again after a timer has been started to adjust
* the expiry time. */
void timer_start(timer* t, int sec)
{
uint64_t newdeadline = current_time() + sec * US_PER_SECOND;
if (timer_is_active(t)){
if (t->deadline < newdeadline) {
timer_delay_us(t, newdeadline - t->deadline);
return;
}
timer_dequeue(t, &timer_main_queue);
}
t->deadline = newdeadline;
timer_enqueue(t, &timer_main_queue, 0);
}
/* Stops the timer. Returns -1 if the timer was not active. */
int timer_stop(timer* t)
{
int ret = timer_is_active(t) ? -1 : 0;
timer_dequeue(t, &timer_main_queue);
return ret;
}
/* Adds additionalsec seconds to the timer.
* Returns -1 and does nothing if the timer was not active. */
int timer_delay(timer* t, int additonalsec)
{
if (!timer_is_active(t)) return -1;
timer_delay_us(t, additonalsec * US_PER_SECOND);
return 0;
}
static uint64_t timer_diff(timer* t, uint64_t time)
{
if (t->deadline <= time) return 0;
return time - t->deadline;
}
/* Returns the time remaining on a timer in seconds.
* Returns -1 if the timer is not active.
* Returns 0 if the timer has expired and will be called upon the next call to timer_poll. */
int timer_time_remaining(timer* t)
{
if (!timer_is_active(t)) return -1;
return timer_diff(t, current_time()) / US_PER_SECOND;
}
bool timer_is_active(timer* t)
{
return t->state != STATE_INACTIVE;
}
/* Single-use timer.
* Creates a new timer, preforms setup and starts it. */
void timer_single(timer_callback cb, void* userarg, int sec)
{
timer* t = timer_new();
timer_setup(t, cb, userarg);
timer_start(t, sec);
}
/* Single-use microsecond timer. */
void timer_us(timer_callback cb, void* userarg, int us)
{
timer* t = timer_new();
timer_setup(t, cb, userarg);
t->deadline = current_time() + us;
t->state = STATE_ACTIVE;
timer_enqueue(t, &timer_us_queue, 0);
}
uint64_t prevtime = 0;
void timer_poll(void)
{
uint64_t time = current_time();
/* Handle millisecond timers */
while (timer_us_queue) {
if (timer_diff(timer_us_queue, time) != 0) break;
timer* t = timer_us_queue;
timer_dequeue(t, &timer_us_queue);
t->cb(0, t->userdata);
timer_delete(t);
}
if (time - prevtime > US_PER_SECOND || prevtime == 0 || prevtime > time) {
/* time moving backwards is just a sanity check */
prevtime = time;
while (timer_main_queue) {
if (timer_diff(timer_main_queue, time) != 0) break;
timer* t = timer_main_queue;
t->state = STATE_CALLBACK;
int rv = t->cb(t, t->userdata);
if (rv != 0) {
timer_dequeue(t, &timer_main_queue);
timer_delete(t);
continue;
}
if (t->state != STATE_ACTIVE) {
timer_dequeue(t, &timer_main_queue);
}
}
}
}
/*** Internal Testing ***/
/* I do not want to expose internals to the public,
* which is why internals testing is done this way. */
void timer_internal_tests(bool (*assert)(bool, char*))
{
}
void timer_debug_print()
{
timer* t = timer_main_queue;
printf("Queue:\n");
while (t) {
printf("%lli (%lli) : %s\n", t->deadline, t->deadline/US_PER_SECOND, (char*)t->userdata);
t = t->_next;
}
}

104
core/timer.h Normal file
View File

@ -0,0 +1,104 @@
/* timer.h
*
* Timing subsystem. Provides deadline timers.
* All times are aliased to a second for efficiency.
*
* Timer Guarantees:
* - The callback will not be called before the timer expires.
* - The callback will be called sometime after the timer expires,
* on a best effort basis.
* - If timer_poll is called at least once a second, the callback
* will be called at most one second after it expires.
*
* Copyright (C) 2013 Tox project All Rights Reserved.
*
* This file is part of Tox.
*
* Tox 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 3 of the License, or
* (at your option) any later version.
*
* Tox 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 Tox. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TIMER_H
#define TIMER_H
#include <stdint.h>
#include <stdbool.h>
#define US_PER_SECOND 1000000 /* 1 s = 10^6 us */
struct timer;
typedef struct timer timer;
/* If time_callback returns a non-zero value, timer t is deleted.
* You may call any of the timer functions within the callback:
* For example, you may call timer_start to restart the timer from
* within a callback. */
typedef int (*timer_callback)(timer* t, void* userarg);
/* Initisalise timer subsystem */
void timer_init(void);
/* Poll. (I will eventually replace all polling in Tox with an async system.) */
void timer_poll(void);
/* Creates a new timer. Does not enqueue/start it. */
timer* timer_new(void);
/* Destroys a timer instance. */
void timer_delete(timer* t);
/* Sets up the timer callback. */
void timer_setup(timer* t, timer_callback cb, void* userarg);
/* Accessor Function. */
void* timer_get_userdata(timer* t);
/* Starts the timer so that it's called in sec seconds in the future from now.
* A non-positive value of sec results in the callback being called immediately.
* This function may be called again after a timer has been started to adjust
* the expiry time. */
void timer_start(timer* t, int sec);
/* Stops the timer. Returns -1 if the timer was not active. */
int timer_stop(timer* t);
/* Adds additionalsec seconds to the timer.
* Returns -1 and does nothing if the timer was not active. */
int timer_delay(timer* t, int additonalsec);
/* Returns the time remaining on a timer in seconds.
* Returns -1 if the timer is not active.
* Returns 0 if the timer has expired and the callback hasn't been called yet. */
int timer_time_remaining(timer* t);
/* Determines if timer is active. Returns TRUE if it is active */
bool timer_is_active(timer* t);
/* Single-use timer.
* Creates a new timer, preforms setup and starts it.
* Callback must return a non-zero value to prevent memory leak. */
void timer_single(timer_callback cb, void* userarg, int sec);
/* Single-use microsecond timer.
* Creates a new timer, preforms setup and starts it.
* Please do not use this when accuracy is not absolutely required.
* Use when one needs to time a period < 1 s.
* Use the more coarse timers above for periods > 5 s.
* WARNING: the callback will be called with NULL as the first argument */
void timer_us(timer_callback cb, void* userarg, int us);
/* Internal Testing */
void timer_internal_tests(bool(*)(bool, char*));
#endif

View File

@ -9,6 +9,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Lossless_UDP_testclient.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Lossless_UDP_testserver.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Lossless_UDP_testserver.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Messenger_test.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Messenger_test.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/crypto_speed_test.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/crypto_speed_test.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/timer_test.cmake)
if(WIN32) if(WIN32)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/nTox_win32.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/nTox_win32.cmake)

View File

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 2.6.0)
project(timer_test C)
set(exe_name timer_test)
add_executable(${exe_name}
timer_test.c)
linkCoreLibraries(${exe_name})

66
testing/timer_test.c Normal file
View File

@ -0,0 +1,66 @@
#include "../core/timer.h"
#include <stdio.h>
#ifdef WINDOWS
#include <windows.h>
#else
#include <unistd.h>
#endif
void mssleep(int ms)
{
#ifdef WINDOWS
Sleep(ms);
#else
usleep(ms * 1000);
#endif
}
int callback(timer* t, void* arg){
printf("%s\n", (char*)arg);
return 1;
}
int repeating(timer* t, void *arg) {
printf("%s\n", (char*)arg);
timer_start(t, 3);
return 0;
}
extern void timer_debug_print();
int main(int argc, char** argv)
{
timer_init();
timer_debug_print();
timer* t = timer_new();
timer_setup(t, &callback, "Long setup method, 4 seconds");
timer_start(t, 4);
timer_debug_print();
timer_single(&repeating, (void*)"This repeats every 3 seconds", 3);
timer_debug_print();
timer_single(&callback, "Short method, 4 seconds", 4);
timer_debug_print();
timer_single(&callback, "1 second", 1);
timer_debug_print();
timer_single(&callback, "15 seconds", 15);
timer_debug_print();
timer_single(&callback, "10 seconds", 10);
timer_debug_print();
timer_us(&callback, "100000us", 100000);
timer_us(&callback, "13s", 13 * US_PER_SECOND);
while (true) {
timer_poll();
mssleep(10);
}
return 0;
}