Skip to content

Commit

Permalink
Merge pull request #129 from UiL-OTS-labs/feature/timer-async-cb
Browse files Browse the repository at this point in the history
Feature/timer async cb
  • Loading branch information
maartenuni authored Nov 4, 2024
2 parents 2000704 + 5aa51b0 commit b66ca90
Show file tree
Hide file tree
Showing 7 changed files with 405 additions and 64 deletions.
8 changes: 5 additions & 3 deletions doc/psy-doc.toml.in
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@ doc_url = "https://docs.gtk.org/pango/"
# The generated files will be placed in the root output directory
content_files = [
"introduction.md",
"building-psylib.md",
"building-psylib-on-linux.md",
"building-psylib-on-windows.md",
"stepping-through-experiment.md",
"drawing-with-psylib-developer.md",
"audio-with-psylib.md",
"building-psylib.md",
"building-psylib-on-linux.md",
"building-psylib-on-windows.md",
"testing-psylib.md",
]

# Additional images referenced by the documentation; their path
# is relative to the content directory specified on the command
# line.
Expand Down
44 changes: 44 additions & 0 deletions doc/testing-psylib.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Title:Testing psylib
SPDX-License-Identifier: MIT
SPDX-FileCopyrightText: 2024 Maarten Duijndam

# Testing psylib

Before running the tests make sure you are able to configure and compile a build first.
In order to run all unit tests and examples - the examples are also tested in CI - you
will have to setup an environment where you can run the tests. If you don't e.g.
libpsy.dll isn't found isn't found on windows. Additionally in order to get the python
(and in the future maybe other) examples, the GI_TYPELIB_PATH must be setup.
meson helps out in this respect. Consider that you have builddir is called "debug" and
your in the root of psylib. You can activate the environment by running:

```bash
meson devenv -C debug
```

Now you can run the tests with: `meson test`, or you can do use one command to run
everything and step out of the debug environment by running:

```bash
meson devenv -C debug meson test
```
In order to run a release instead of debug, replace debug with release in the commands
above.

## Issues on Ubuntu-22.04 with clang address sanitizers

The test can be run using the ASAN and LSAN tools in order to inspect
issues related to memory issues. On Ubuntu 22.04 this sometimes crashes with a great
number of errors being printed.

```
AddressSanitizer:DEADLYSIGNAL
```

There is a [work around](https://github.com/actions/runner-images/issues/9524#issuecomment-2002475952)
posted for this, because it came out in a lot of CI of other projects.
The workaround with the following command:

```bash
sudo sysctl vm.mmap_rnd_bits=28
```
5 changes: 5 additions & 0 deletions psy/psy-init.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ initializer_constructed(GObject *obj)

init_count++;

g_info("Initializer::Initializing psylib count: %d", init_count);

if (init_count == 1) {

// stuff we always init
Expand Down Expand Up @@ -89,6 +91,9 @@ initializer_finalize(GObject *obj)

g_mutex_lock(&init_mutex);
init_count--;

g_info("Initializer::Deinitializing psylib count: %d", init_count);

if (init_count == 0) {
// stuff we always deinit
timer_private_stop_timer_thread();
Expand Down
28 changes: 23 additions & 5 deletions psy/psy-timer-private.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <windows.h>
#endif

#define TEN_MS 10000

/* *********** globals *************** */

static PsyTimerThread *g_timer_thread;
Expand Down Expand Up @@ -40,10 +42,15 @@ typedef struct ThreadData {
static gint
compare_timer_time_stamps_values(gconstpointer t1, gconstpointer t2)
{
const PsyTimePoint *tp1 = psy_timer_get_fire_time(PSY_TIMER((gpointer) t1));
const PsyTimePoint *tp2 = psy_timer_get_fire_time(PSY_TIMER((gpointer) t2));
PsyTimePoint *tp1 = psy_timer_get_fire_time(PSY_TIMER((gpointer) t1));
PsyTimePoint *tp2 = psy_timer_get_fire_time(PSY_TIMER((gpointer) t2));

gboolean ret = psy_compare_time_point(tp1, tp2);

psy_time_point_free(tp1);
psy_time_point_free(tp2);

return psy_compare_time_point(tp1, tp2);
return ret;
}

#if !GLIB_CHECK_VERSION(2, 76, 0)
Expand Down Expand Up @@ -212,12 +219,14 @@ psy_timer_thread_fire_timers(PsyTimerThread *self)
PsyTimePoint *tp = psy_timer_get_fire_time(first);

if (psy_time_point_greater_equal(now, tp)) {
psy_timer_fire(first, psy_timer_get_fire_time(first));
psy_timer_fire_async_cb(first, tp);
psy_timer_fire(first, tp);
g_ptr_array_remove_index(self->timers, 0);

psy_time_point_free(now);
psy_time_point_free(now_plus_busy_dur);
break;
psy_time_point_free(tp);
continue;
}

ThreadData *msg = g_async_queue_try_pop(self->queue);
Expand All @@ -227,6 +236,7 @@ psy_timer_thread_fire_timers(PsyTimerThread *self)

psy_time_point_free(now);
psy_time_point_free(now_plus_busy_dur);
psy_time_point_free(tp);
}
}

Expand Down Expand Up @@ -256,6 +266,7 @@ psy_timer_thread_check_timers(PsyTimerThread *self)
ret = TRUE;
}
psy_duration_free(dur);
psy_time_point_free(tp);
psy_time_point_free(now);
}

Expand Down Expand Up @@ -291,6 +302,10 @@ timer_thread(gpointer data)
{
PsyTimerThread *self = data;

g_info("TimerThread %p, with thread = %p is running",
(gpointer) self,
(gpointer) self->thread);

while (self->running) {
ThreadData *data = g_async_queue_timeout_pop(self->queue, 1000);
if (data) {
Expand All @@ -313,11 +328,14 @@ psy_timer_thread_join(PsyTimerThread *self)
return;

// Send stop message
g_debug("Sending stop msg to timer thread");
ThreadData *data = thread_data_new(MSG_STOP, self, NULL);

g_async_queue_push(self->queue, data);

g_debug("joining timer thread");
g_thread_join(self->thread);
g_debug("timer thread joined");
self->thread = NULL;
}

Expand Down
94 changes: 77 additions & 17 deletions psy/psy-timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ typedef struct _PsyTimer {

GAsyncQueue *queue;

PsyTimerAsyncCb callback;
gpointer callback_data;

} PsyTimer;

typedef enum {
Expand Down Expand Up @@ -133,21 +136,6 @@ thread_default_fire(FireData *data)
return G_SOURCE_REMOVE;
}

void
psy_timer_fire(PsyTimer *self, PsyTimePoint *tp)
{
FireData *data = g_new(FireData, 1);

data->fire_time = psy_time_point_copy(tp);
data->timer = self;

g_main_context_invoke_full(self->context,
G_PRIORITY_DEFAULT,
G_SOURCE_FUNC(thread_default_fire),
data,
(GDestroyNotify) fire_data_free);
}

static void
psy_timer_class_init(PsyTimerClass *klass)
{
Expand Down Expand Up @@ -266,15 +254,16 @@ psy_timer_set_fire_time(PsyTimer *self, PsyTimePoint *tp)
*
* Gets the timepoint when this timer is set
*
* Returns:(nullable)(transfer none):The time for when this timer is/was
* Returns:(nullable)(transfer full):The time for when this timer is/was
* set.
*/
PsyTimePoint *
psy_timer_get_fire_time(PsyTimer *self)
{
g_return_val_if_fail(PSY_IS_TIMER((PsyTimer *) self), NULL);

return self->fire_time;
return self->fire_time != NULL ? psy_time_point_copy(self->fire_time)
: NULL;
}

/**
Expand All @@ -294,6 +283,77 @@ psy_timer_cancel(PsyTimer *self)
g_clear_pointer(&self->fire_time, psy_time_point_free);
}

/**
* psy_timer_set_async_fire_cb:
* @cb:(nullable)(closure data)(scope forever): a callback to be called
* @data: the data passed to the callback
*
* You may only set this member when the fire-time is not yet set.
* This callback is called from the timer thread, hence, you must take care not
* to run in any thread related issues when operation on/with data. You should
* only call this function when its fire time isn't set yet, as that could
* complicate stuff.
* The thread that monitors the timers will first undertake the steps to emit
* the fired signal, as that is guaranteed pretty quickly. Only then it will
* call this callback asynchronously. It is advised that this callback is non
* blocking, as a blocking callback will mess with other timer scheduled .
*
* Returns: TRUE when the callback was successfully set.
*/
gboolean
psy_timer_set_async_fire_cb(PsyTimer *self, PsyTimerAsyncCb cb, gpointer data)
{
g_return_val_if_fail(PSY_IS_TIMER(self), FALSE);

if (self->fire_time) { // cancel ongoing operations first
g_warning("Unable to set callback when timer is already scheduled.");
return FALSE;
}

self->callback = cb;
self->callback_data = data;

return TRUE;
}

/**
* psy_timer_fire:
* @self, the timer to fire
* @tp: The timepoint at which the timer should be fired
*
* Fire the timer in the thread default context at the time the timer was
* created.
*/
void
psy_timer_fire(PsyTimer *self, PsyTimePoint *tp)
{
FireData *data = g_new(FireData, 1);

data->fire_time = psy_time_point_copy(tp);
data->timer = self;

g_main_context_invoke_full(self->context,
G_PRIORITY_DEFAULT,
G_SOURCE_FUNC(thread_default_fire),
data,
(GDestroyNotify) fire_data_free);
}

/**
* psy_timer_fire_async_cb:(skip)
*
* fires the async callback
*/
void
psy_timer_fire_async_cb(PsyTimer *self, PsyTimePoint *tp)
{
g_return_if_fail(PSY_IS_TIMER(self));

if (self->callback) {
self->callback(tp, self->callback_data);
}
}

/**
* psy_timer_get_queue:(skip)
* @self: the timer
Expand Down
19 changes: 18 additions & 1 deletion psy/psy-timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,28 @@ psy_timer_get_fire_time(PsyTimer *self);
G_MODULE_EXPORT void
psy_timer_cancel(PsyTimer *self);

/*The next functions are internal*/
/**
* psy_timer_async_cb:
* @tp:The timepoint at which the timer was set.
* @data:(closure):
*
* This is a callback that will be called from a thread that monitors the
* timers. The callback should last as short as possible in order not to hinder
* other timers.
*/
typedef void (*PsyTimerAsyncCb)(PsyTimePoint *tp, gpointer data);

G_MODULE_EXPORT gboolean
psy_timer_set_async_fire_cb(PsyTimer *self, PsyTimerAsyncCb cb, gpointer data);

/* **** The next functions are internal **** */

void
psy_timer_fire(PsyTimer *self, PsyTimePoint *tp);

void
psy_timer_fire_async_cb(PsyTimer *self, PsyTimePoint *tp);

GAsyncQueue *
psy_timer_get_queue(PsyTimer *self);

Expand Down
Loading

0 comments on commit b66ca90

Please sign in to comment.