Compare commits

...

8 Commits

Author SHA1 Message Date
Slava Monich
ccf3e1237c Version 1.0.41 2020-04-14 12:40:05 +03:00
Slava Monich
e02f00fd41 Merge pull request #45 from monich/idle-pool
Stop using GUtilIdlePool
2020-04-14 12:34:42 +03:00
Slava Monich
e45e640f3c [gbinder] Stop using GUtilIdlePool. JB#49512
It won't work with a non-glib event loop. And one of those objects
turned out to actually be unused.
2020-04-11 02:26:46 +03:00
Slava Monich
75e8015c43 [gbinder] Added internal event loop utilities. JB#49512
gbinder_idle_callback_schedule_new = gbinder_idle_callback_new +
and gbinder_idle_callback_schedule

gbinder_idle_callback_destroy = gbinder_idle_callback_cancel +
gbinder_idle_callback_unref
2020-04-11 02:22:22 +03:00
Slava Monich
2c52fcc156 Version 1.0.40 2020-04-09 17:24:06 +03:00
Slava Monich
dc5b51a41f Merge pull request #44 from monich/event-loop
Pluggable event loop integration
2020-04-09 17:18:29 +03:00
Slava Monich
d12cee4690 [unit] Added eventloop test. JB#49512 2020-04-06 17:22:55 +03:00
Slava Monich
a74c4ac148 [gbinder] Virtualize event loop. JB#49512
This makes it possible to make libgbinder work with e.g. Qt event loop
based on QEventDispatcherUNIX (rather than QEventDispatcherGlib).
2020-04-04 20:26:06 +03:00
16 changed files with 1012 additions and 78 deletions

View File

@@ -16,7 +16,7 @@
VERSION_MAJOR = 1
VERSION_MINOR = 0
VERSION_RELEASE = 39
VERSION_RELEASE = 41
# Version for pkg-config
PCVERSION = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_RELEASE)
@@ -79,6 +79,7 @@ SRC = \
gbinder_cleanup.c \
gbinder_client.c \
gbinder_driver.c \
gbinder_eventloop.c \
gbinder_io_32.c \
gbinder_io_64.c \
gbinder_ipc.c \

12
debian/changelog vendored
View File

@@ -1,3 +1,15 @@
libgbinder (1.0.41) unstable; urgency=low
* Stop using GUtilIdlePool
-- Slava Monich <slava.monich@jolla.com> Tue, 14 Apr 2020 12:36:54 +0300
libgbinder (1.0.40) unstable; urgency=low
* Support integration with non-glib event loops
-- Slava Monich <slava.monich@jolla.com> Thu, 09 Apr 2020 17:22:12 +0300
libgbinder (1.0.39) unstable; urgency=low
* Adapted to side-by-side linking

194
include/gbinder_eventloop.h Normal file
View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2020 Jolla Ltd.
* Copyright (C) 2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders 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 THE COPYRIGHT HOLDERS OR CONTRIBUTORS
* 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.
*/
#ifndef GBINDER_EVENTLOOP_H
#define GBINDER_EVENTLOOP_H
#include "gbinder_types.h"
G_BEGIN_DECLS
/* Since 1.0.40 */
typedef struct gbinder_eventloop_integration GBinderEventLoopIntegration;
typedef void (*GBinderEventLoopCallbackFunc)(gpointer data);
typedef struct gbinder_eventloop_timeout {
const GBinderEventLoopIntegration* eventloop;
} GBinderEventLoopTimeout;
typedef struct gbinder_eventloop_callback {
const GBinderEventLoopIntegration* eventloop;
} GBinderEventLoopCallback;
/**
* Main event loop integration. There is only one main event loop in the
* process (by definition).
*
* By default, GLib event loop is being used for callbacks and timeouts.
*
* It may be necessary to replace it with e.g. Qt event loop. Quite often
* Qt event loop is implemented by QEventDispatcherGlib which is sitting
* on top of GLib event and therefore works with the default implementation.
* But it won't work with e.g. QEventDispatcherUNIX.
*
* For Qt programs that use QEventDispatcherUNIX, it needs to be replaced
* with the one provided by libqbinder.
*/
typedef struct gbinder_eventloop_integration {
/**
* timeout_add
*
* Sets a function to be called at regular intervals (in milliseconds).
* If the function returns G_SOURCE_REMOVE, timeout is automatically
* destroyed (you must not call timeout_remove in this case). If the
* function returns G_SOURCE_CONTINUE, it will be called again after
* the same interval.
*/
GBinderEventLoopTimeout* (*timeout_add)(guint millis, GSourceFunc func,
gpointer data);
/**
* timeout_remove
*
* Removes a pending timeout and destroys it. The caller makes sure that
* argument is not NULL. Note that timeout is automatically destroyed if
* the callback function returns G_SOURCE_REMOVE.
*/
void (*timeout_remove)(GBinderEventLoopTimeout* timeout);
/**
* callback_new
*
* Creates a callback object. It returns you a reference, you must
* eventually pass the returned object to callback_unref to drop
* this reference.
*
* Note that it doesn't automatically schedule the callback. You
* must explicitly call callback_schedule to actually schedule it.
* The finalize function is invoked regardless of whether callback
* was cancelled or not.
*/
GBinderEventLoopCallback* (*callback_new)(GBinderEventLoopCallbackFunc fun,
gpointer data, GDestroyNotify finalize);
/**
* callback_ref
*
* Increments the reference count. That prevents the object from being
* deleted before you drop this reference. The caller makes sure that
* argument is not NULL.
*/
void (*callback_ref)(GBinderEventLoopCallback* cb);
/**
* callback_unref
*
* Decrements the reference count (drops the reference). When reference
* count reaches zero, the object gets deleted. The caller makes sure
* that argument is not NULL.
*
* Note that calling callback_schedule temporarily adds an internal
* reference until the callback is invoked or callback_cancel is called,
* whichever happens first.
*/
void (*callback_unref)(GBinderEventLoopCallback* cb);
/**
* callback_schedule
*
* Schedules the callback to be invoked in the main loop at some point
* in the future (but as soon as possible). The caller makes sure that
* argument is not NULL.
*
* This adds an internal reference to the GBinderEventLoopCallback object
* until the callback is invoked or callback_cancel is called, whichever
* happens first.
*/
void (*callback_schedule)(GBinderEventLoopCallback* cb);
/**
* callback_cancel
*
* Makes sure that callback won't be invoked (if it hasn't been
* invoked yet) and drops the internal reference. Does nothing
* if the callback has already been invoked. The caller makes sure that
* argument is not NULL.
*/
void (*callback_cancel)(GBinderEventLoopCallback* cb);
/**
* cleanup
*
* This function is called when event loop integration is being replaced
* with a different one, or libgbinder is being unloaded.
*/
void (*cleanup)(void);
/* Padding for future expansion */
void (*_reserved1)(void);
void (*_reserved2)(void);
void (*_reserved3)(void);
void (*_reserved4)(void);
void (*_reserved5)(void);
void (*_reserved6)(void);
void (*_reserved7)(void);
void (*_reserved8)(void);
void (*_reserved9)(void);
/*
* api_level will remain zero (and ignored) until we run out of
* the above placeholders. Hopefully, forever.
*/
int api_level;
} GBinderEventLoopIntegration;
/**
* gbinder_eventloop_set should be called before libgbinder creates any of
* its internal threads. And it must be done from the main thread.
*/
void
gbinder_eventloop_set(
const GBinderEventLoopIntegration* loop);
G_END_DECLS
#endif /* GBINDER_EVENTLOOP_H */
/*
* Local Variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/

View File

@@ -1,14 +1,16 @@
Name: libgbinder
Version: 1.0.39
Version: 1.0.41
Release: 0
Summary: Binder client library
Group: Development/Libraries
License: BSD
URL: https://github.com/mer-hybris/libgbinder
Source: %{name}-%{version}.tar.bz2
Requires: libglibutil >= 1.0.35
%define libglibutil_version 1.0.35
BuildRequires: pkgconfig(glib-2.0)
BuildRequires: pkgconfig(libglibutil) >= 1.0.35
BuildRequires: pkgconfig(libglibutil) >= %{libglibutil_version}
Requires: libglibutil >= %{libglibutil_version}
Requires(post): /sbin/ldconfig
Requires(postun): /sbin/ldconfig

View File

@@ -1,6 +1,6 @@
/*
* Copyright (C) 2018-2019 Jolla Ltd.
* Copyright (C) 2018-2019 Slava Monich <slava.monich@jolla.com>
* Copyright (C) 2018-2020 Jolla Ltd.
* Copyright (C) 2018-2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
@@ -14,8 +14,8 @@
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* 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
@@ -33,6 +33,7 @@
#include "gbinder_servicemanager_p.h"
#include "gbinder_rpc_protocol.h"
#include "gbinder_servicepoll.h"
#include "gbinder_eventloop_p.h"
#include "gbinder_log.h"
#include <gbinder_client.h>
@@ -48,7 +49,7 @@ typedef struct gbinder_defaultservicemanager_watch {
GBinderServicePoll* poll;
char* name;
gulong handler_id;
guint notify_id;
GBinderEventLoopTimeout* notify;
} GBinderDefaultServiceManagerWatch;
typedef GBinderServiceManagerClass GBinderDefaultServiceManagerClass;
@@ -99,9 +100,9 @@ gbinder_defaultservicemanager_watch_proc(
GBinderServiceManager* manager =
gbinder_servicepoll_manager(watch->poll);
if (watch->notify_id) {
g_source_remove(watch->notify_id);
watch->notify_id = 0;
if (watch->notify) {
gbinder_timeout_remove(watch->notify);
watch->notify = NULL;
}
gbinder_servicemanager_service_registered(manager, name_added);
}
@@ -116,8 +117,8 @@ gbinder_defaultservicemanager_watch_notify(
GBinderServiceManager* manager = gbinder_servicepoll_manager(watch->poll);
char* name = g_strdup(watch->name);
GASSERT(watch->notify_id);
watch->notify_id = 0;
GASSERT(watch->notify);
watch->notify = NULL;
gbinder_servicemanager_service_registered(manager, name);
g_free(name);
return G_SOURCE_REMOVE;
@@ -130,9 +131,7 @@ gbinder_defaultservicemanager_watch_free(
{
GBinderDefaultServiceManagerWatch* watch = user_data;
if (watch->notify_id) {
g_source_remove(watch->notify_id);
}
gbinder_timeout_remove(watch->notify);
gbinder_servicepoll_remove_handler(watch->poll, watch->handler_id);
gbinder_servicepoll_unref(watch->poll);
g_free(watch->name);
@@ -258,8 +257,8 @@ gbinder_defaultservicemanager_watch(
g_hash_table_replace(self->watch_table, watch->name, watch);
if (gbinder_servicepoll_is_known_name(watch->poll, name)) {
watch->notify_id =
g_idle_add(gbinder_defaultservicemanager_watch_notify, watch);
watch->notify = gbinder_idle_add
(gbinder_defaultservicemanager_watch_notify, watch);
}
return TRUE;
}

347
src/gbinder_eventloop.c Normal file
View File

@@ -0,0 +1,347 @@
/*
* Copyright (C) 2020 Jolla Ltd.
* Copyright (C) 2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders 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 THE COPYRIGHT HOLDERS OR CONTRIBUTORS
* 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 "gbinder_eventloop_p.h"
#include <gutil_macros.h>
#define GBINDER_DEFAULT_EVENTLOOP (&gbinder_eventloop_glib)
static const GBinderEventLoopIntegration gbinder_eventloop_glib;
/*==========================================================================*
* GLib event loop integration
*==========================================================================*/
typedef struct gbinder_eventloop_glib_timeout {
GBinderEventLoopTimeout timeout;
guint id;
GSourceFunc func;
gpointer data;
} GBinderEventLoopTimeoutGLib;
typedef struct gbinder_eventloop_glib_callback {
GSource source;
GBinderEventLoopCallback callback;
} GBinderEventLoopCallbackGLib;
static
inline
GBinderEventLoopTimeoutGLib*
gbinder_eventloop_glib_timeout_cast(
GBinderEventLoopTimeout* timeout)
{
return G_CAST(timeout,GBinderEventLoopTimeoutGLib,timeout);
}
static
inline
GSource*
gbinder_eventloop_glib_callback_source(
GBinderEventLoopCallback* callback)
{
return &(G_CAST(callback,GBinderEventLoopCallbackGLib,callback)->source);
}
static
gboolean
gbinder_eventloop_glib_timeout_callback(
gpointer data)
{
GBinderEventLoopTimeoutGLib* timeout = data;
return timeout->func(timeout->data);
}
static
void
gbinder_eventloop_glib_timeout_finalize(
gpointer data)
{
g_slice_free1(sizeof(GBinderEventLoopTimeoutGLib), data);
}
static
GBinderEventLoopTimeout*
gbinder_eventloop_glib_timeout_add(
guint interval,
GSourceFunc func,
gpointer data)
{
GBinderEventLoopTimeoutGLib* impl =
g_slice_new(GBinderEventLoopTimeoutGLib);
impl->timeout.eventloop = &gbinder_eventloop_glib;
impl->func = func;
impl->data = data;
impl->id = g_timeout_add_full(G_PRIORITY_DEFAULT, interval,
gbinder_eventloop_glib_timeout_callback, impl,
gbinder_eventloop_glib_timeout_finalize);
return &impl->timeout;
}
static
void
gbinder_eventloop_glib_timeout_remove(
GBinderEventLoopTimeout* timeout)
{
g_source_remove(gbinder_eventloop_glib_timeout_cast(timeout)->id);
}
static
gboolean
gbinder_eventloop_glib_callback_prepare(
GSource* source,
gint* timeout)
{
*timeout = 0;
return TRUE;
}
static
gboolean
gbinder_eventloop_glib_callback_check(
GSource* source)
{
return TRUE;
}
static
gboolean
gbinder_eventloop_glib_callback_dispatch(
GSource* source,
GSourceFunc callback,
gpointer user_data)
{
((GBinderEventLoopCallbackFunc)callback)(user_data);
return G_SOURCE_REMOVE;
}
static
GBinderEventLoopCallback*
gbinder_eventloop_glib_callback_new(
GBinderEventLoopCallbackFunc func,
gpointer data,
GDestroyNotify finalize)
{
static GSourceFuncs callback_funcs = {
gbinder_eventloop_glib_callback_prepare,
gbinder_eventloop_glib_callback_check,
gbinder_eventloop_glib_callback_dispatch
};
GBinderEventLoopCallbackGLib* impl = (GBinderEventLoopCallbackGLib*)
g_source_new(&callback_funcs, sizeof(GBinderEventLoopCallbackGLib));
impl->callback.eventloop = &gbinder_eventloop_glib;
g_source_set_callback(&impl->source, (GSourceFunc) func, data, finalize);
return &impl->callback;
}
static
void
gbinder_eventloop_glib_callback_ref(
GBinderEventLoopCallback* cb)
{
g_source_ref(gbinder_eventloop_glib_callback_source(cb));
}
static
void
gbinder_eventloop_glib_callback_unref(
GBinderEventLoopCallback* cb)
{
g_source_unref(gbinder_eventloop_glib_callback_source(cb));
}
static
void
gbinder_eventloop_glib_callback_schedule(
GBinderEventLoopCallback* cb)
{
static GMainContext* context = NULL;
if (!context) context = g_main_context_default();
g_source_attach(gbinder_eventloop_glib_callback_source(cb), context);
}
static
void
gbinder_eventloop_glib_callback_cancel(
GBinderEventLoopCallback* cb)
{
g_source_destroy(gbinder_eventloop_glib_callback_source(cb));
}
static
void
gbinder_eventloop_glib_cleanup(
void)
{
}
static const GBinderEventLoopIntegration gbinder_eventloop_glib = {
gbinder_eventloop_glib_timeout_add,
gbinder_eventloop_glib_timeout_remove,
gbinder_eventloop_glib_callback_new,
gbinder_eventloop_glib_callback_ref,
gbinder_eventloop_glib_callback_unref,
gbinder_eventloop_glib_callback_schedule,
gbinder_eventloop_glib_callback_cancel,
gbinder_eventloop_glib_cleanup
};
/*==========================================================================*
* Internal interface
*==========================================================================*/
static const GBinderEventLoopIntegration* gbinder_eventloop =
GBINDER_DEFAULT_EVENTLOOP;
GBinderEventLoopTimeout*
gbinder_timeout_add(
guint interval,
GSourceFunc function,
gpointer data)
{
return gbinder_eventloop->timeout_add(interval, function, data);
}
GBinderEventLoopTimeout*
gbinder_idle_add(
GSourceFunc function,
gpointer data)
{
return gbinder_eventloop->timeout_add(0, function, data);
}
void
gbinder_timeout_remove(
GBinderEventLoopTimeout* timeout)
{
if (timeout) {
timeout->eventloop->timeout_remove(timeout);
}
}
GBinderEventLoopCallback*
gbinder_idle_callback_new(
GBinderEventLoopCallbackFunc func,
gpointer data,
GDestroyNotify finalize)
{
return gbinder_eventloop->callback_new(func, data, finalize);
}
GBinderEventLoopCallback*
gbinder_idle_callback_schedule_new(
GBinderEventLoopCallbackFunc func,
gpointer data,
GDestroyNotify finalize)
{
GBinderEventLoopCallback* cb =
gbinder_eventloop->callback_new(func, data, finalize);
gbinder_idle_callback_schedule(cb);
return cb;
}
GBinderEventLoopCallback*
gbinder_idle_callback_ref(
GBinderEventLoopCallback* cb)
{
if (cb) {
cb->eventloop->callback_ref(cb);
return cb;
}
return NULL;
}
void
gbinder_idle_callback_unref(
GBinderEventLoopCallback* cb)
{
if (cb) {
cb->eventloop->callback_unref(cb);
}
}
void
gbinder_idle_callback_schedule(
GBinderEventLoopCallback* cb)
{
if (cb) {
cb->eventloop->callback_schedule(cb);
}
}
void
gbinder_idle_callback_cancel(
GBinderEventLoopCallback* cb)
{
if (cb) {
cb->eventloop->callback_cancel(cb);
}
}
void
gbinder_idle_callback_destroy(
GBinderEventLoopCallback* cb)
{
if (cb) {
const GBinderEventLoopIntegration* eventloop = cb->eventloop;
eventloop->callback_cancel(cb);
eventloop->callback_unref(cb);
}
}
void
gbinder_eventloop_set(
const GBinderEventLoopIntegration* loop)
{
if (!loop) loop = GBINDER_DEFAULT_EVENTLOOP;
if (gbinder_eventloop != loop) {
const GBinderEventLoopIntegration* prev = gbinder_eventloop;
gbinder_eventloop = loop;
prev->cleanup();
}
}
/*
* Local Variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/

103
src/gbinder_eventloop_p.h Normal file
View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2020 Jolla Ltd.
* Copyright (C) 2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders 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 THE COPYRIGHT HOLDERS OR CONTRIBUTORS
* 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.
*/
#ifndef GBINDER_EVENTLOOP_PRIVATE_H
#define GBINDER_EVENTLOOP_PRIVATE_H
#include "gbinder_eventloop.h"
G_GNUC_INTERNAL
GBinderEventLoopTimeout*
gbinder_timeout_add(
guint millis,
GSourceFunc func,
gpointer data);
G_GNUC_INTERNAL
GBinderEventLoopTimeout*
gbinder_idle_add(
GSourceFunc func,
gpointer data);
G_GNUC_INTERNAL
void
gbinder_timeout_remove(
GBinderEventLoopTimeout* timeout);
G_GNUC_INTERNAL
GBinderEventLoopCallback*
gbinder_idle_callback_new(
GBinderEventLoopCallbackFunc func,
gpointer data,
GDestroyNotify destroy);
G_GNUC_INTERNAL
GBinderEventLoopCallback*
gbinder_idle_callback_schedule_new(
GBinderEventLoopCallbackFunc func,
gpointer data,
GDestroyNotify destroy);
G_GNUC_INTERNAL
GBinderEventLoopCallback*
gbinder_idle_callback_ref(
GBinderEventLoopCallback* cb);
G_GNUC_INTERNAL
void
gbinder_idle_callback_unref(
GBinderEventLoopCallback* cb);
G_GNUC_INTERNAL
void
gbinder_idle_callback_schedule(
GBinderEventLoopCallback* cb);
G_GNUC_INTERNAL
void
gbinder_idle_callback_cancel(
GBinderEventLoopCallback* cb);
G_GNUC_INTERNAL
void
gbinder_idle_callback_destroy(
GBinderEventLoopCallback* cb);
#endif /* GBINDER_EVENTLOOP_PRIVATE_H */
/*
* Local Variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/

View File

@@ -43,10 +43,10 @@
#include "gbinder_remote_object_p.h"
#include "gbinder_remote_reply_p.h"
#include "gbinder_remote_request_p.h"
#include "gbinder_eventloop_p.h"
#include "gbinder_writer.h"
#include "gbinder_log.h"
#include <gutil_idlepool.h>
#include <gutil_macros.h>
#include <unistd.h>
@@ -60,7 +60,6 @@ struct gbinder_ipc_priv {
GBinderIpc* self;
GThreadPool* tx_pool;
GHashTable* tx_table;
GMainContext* context;
char* key;
GBinderObjectRegistry object_registry;
@@ -175,7 +174,7 @@ typedef struct gbinder_ipc_tx_priv {
GBinderIpcTxPrivFunc fn_exec;
GBinderIpcTxPrivFunc fn_done;
GBinderIpcTxPrivFunc fn_free;
GSource* completion;
GBinderEventLoopCallback* completion;
} GBinderIpcTxPriv;
typedef struct gbinder_ipc_tx_internal {
@@ -406,7 +405,7 @@ gbinder_ipc_looper_unref(
}
static
gboolean
void
gbinder_ipc_looper_tx_handle(
gpointer data)
{
@@ -480,7 +479,6 @@ gbinder_ipc_looper_tx_handle(
if (write(tx->pipefd[1], &done, sizeof(done)) <= 0) {
GWARN("Failed to wake up the looper");
}
return G_SOURCE_REMOVE;
}
static
@@ -593,13 +591,11 @@ gbinder_ipc_looper_transact(
GBinderIpcPriv* priv = ipc->priv;
struct pollfd fds[2];
guint8 done = 0;
GSource* source = g_idle_source_new();
gboolean was_blocked = FALSE;
/* Let GBinderLocalObject handle the transaction on the main thread */
g_source_set_callback(source, gbinder_ipc_looper_tx_handle,
gbinder_ipc_looper_tx_ref(tx), gbinder_ipc_looper_tx_done);
g_source_attach(source, priv->context);
GBinderEventLoopCallback* callback =
gbinder_idle_callback_schedule_new(gbinder_ipc_looper_tx_handle,
gbinder_ipc_looper_tx_ref(tx), gbinder_ipc_looper_tx_done);
/* Wait for either transaction completion or looper shutdown */
memset(fds, 0, sizeof(fds));
@@ -685,8 +681,7 @@ gbinder_ipc_looper_transact(
gbinder_ipc_looper_tx_unref(tx, FALSE);
}
g_source_destroy(source);
g_source_unref(source);
gbinder_idle_callback_destroy(callback);
if (was_blocked) {
guint n;
@@ -1197,7 +1192,7 @@ gbinder_ipc_tx_free(
GBinderIpc* self = pub->ipc;
GBinderIpcPriv* priv = self->priv;
g_source_unref(tx->completion);
gbinder_idle_callback_unref(tx->completion);
g_hash_table_remove(priv->tx_table, GINT_TO_POINTER(pub->id));
tx->fn_free(tx);
@@ -1206,7 +1201,7 @@ gbinder_ipc_tx_free(
}
static
gboolean
void
gbinder_ipc_tx_done(
gpointer data)
{
@@ -1216,7 +1211,6 @@ gbinder_ipc_tx_done(
if (!pub->cancelled) {
tx->fn_done(tx);
}
return G_SOURCE_REMOVE;
}
static
@@ -1247,8 +1241,7 @@ gbinder_ipc_tx_priv_init(
priv->fn_exec = fn_exec;
priv->fn_done = fn_done;
priv->fn_free = fn_free;
priv->completion = g_idle_source_new();
g_source_set_callback(priv->completion, gbinder_ipc_tx_done, priv,
priv->completion = gbinder_idle_callback_new(gbinder_ipc_tx_done, priv,
gbinder_ipc_tx_free);
}
@@ -1425,8 +1418,6 @@ gbinder_ipc_tx_proc(
gpointer object)
{
GBinderIpcTxPriv* tx = data;
GBinderIpc* self = GBINDER_IPC(object);
GBinderIpcPriv* priv = self->priv;
if (!tx->pub.cancelled) {
tx->fn_exec(tx);
@@ -1435,7 +1426,7 @@ gbinder_ipc_tx_proc(
}
/* The result is handled by the main thread */
g_source_attach(tx->completion, priv->context);
gbinder_idle_callback_schedule(tx->completion);
}
/*==========================================================================*
@@ -1641,14 +1632,12 @@ gbinder_ipc_init(
g_mutex_init(&priv->looper_mutex);
g_mutex_init(&priv->local_objects_mutex);
g_mutex_init(&priv->remote_objects_mutex);
priv->context = g_main_context_default();
priv->tx_table = g_hash_table_new(g_direct_hash, g_direct_equal);
priv->tx_pool = g_thread_pool_new(gbinder_ipc_tx_proc, self,
GBINDER_IPC_MAX_TX_THREADS, FALSE, NULL);
priv->object_registry.f = &object_registry_functions;
priv->self = self;
self->priv = priv;
self->pool = gutil_idle_pool_new();
}
static
@@ -1729,7 +1718,6 @@ gbinder_ipc_finalize(
}
GASSERT(!g_hash_table_size(priv->tx_table));
g_hash_table_unref(priv->tx_table);
gutil_idle_pool_unref(self->pool);
gbinder_driver_unref(self->driver);
g_free(priv->key);
G_OBJECT_CLASS(gbinder_ipc_parent_class)->finalize(object);
@@ -1795,11 +1783,9 @@ gbinder_ipc_exit()
}
for (k = tx_keys; k; k = k->next) {
GBinderIpcTxPriv* tx = g_hash_table_lookup(priv->tx_table, k->data);
GSource* source = g_source_ref(tx->completion);
GVERBOSE_("tx %lu", tx->pub.id);
g_source_destroy(source);
g_source_unref(source);
gbinder_idle_callback_cancel(tx->completion);
}
/* The above loop must destroy all uncompleted transactions */
@@ -1830,6 +1816,7 @@ gbinder_ipc_exit()
g_slist_free_full(local_objs, g_object_unref);
}
g_slist_free_full(ipcs, g_object_unref);
gbinder_eventloop_set(NULL);
}
/*

View File

@@ -1,6 +1,6 @@
/*
* Copyright (C) 2018-2019 Jolla Ltd.
* Copyright (C) 2018-2019 Slava Monich <slava.monich@jolla.com>
* Copyright (C) 2018-2020 Jolla Ltd.
* Copyright (C) 2018-2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
@@ -14,8 +14,8 @@
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* 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
@@ -42,7 +42,6 @@ struct gbinder_ipc {
GObject object;
GBinderIpcPriv* priv;
GBinderDriver* driver;
GUtilIdlePool* pool;
const char* dev;
};

View File

@@ -36,13 +36,13 @@
#include "gbinder_client_p.h"
#include "gbinder_local_object_p.h"
#include "gbinder_remote_object_p.h"
#include "gbinder_eventloop_p.h"
#include "gbinder_driver.h"
#include "gbinder_ipc.h"
#include "gbinder_log.h"
#include <gbinder_client.h>
#include <gutil_idlepool.h>
#include <gutil_misc.h>
#include <errno.h>
@@ -62,8 +62,10 @@ struct gbinder_servicemanager_priv {
GHashTable* watch_table;
gulong death_id;
gboolean present;
guint presence_check_id;
GBinderEventLoopTimeout* presence_check;
guint presence_check_delay_ms;
GBinderEventLoopCallback* autorelease_cb;
GSList* autorelease;
};
G_DEFINE_ABSTRACT_TYPE(GBinderServiceManager, gbinder_servicemanager,
@@ -275,9 +277,9 @@ gbinder_servicemanager_reanimated(
{
GBinderServiceManagerPriv* priv = self->priv;
if (priv->presence_check_id) {
g_source_remove(priv->presence_check_id);
priv->presence_check_id = 0;
if (priv->presence_check) {
gbinder_timeout_remove(priv->presence_check);
priv->presence_check = NULL;
}
GINFO("Service manager %s has appeared", self->dev);
/* Re-arm the watches */
@@ -317,13 +319,14 @@ gbinder_servicemanager_presense_check_timer(
gbinder_servicemanager_ref(self);
if (gbinder_remote_object_reanimate(remote)) {
/* Done */
priv->presence_check_id = 0;
priv->presence_check = NULL;
gbinder_servicemanager_reanimated(self);
result = G_SOURCE_REMOVE;
} else if (priv->presence_check_delay_ms < PRESENSE_WAIT_MS_MAX) {
priv->presence_check_delay_ms += PRESENSE_WAIT_MS_STEP;
priv->presence_check_id = g_timeout_add(priv->presence_check_delay_ms,
gbinder_servicemanager_presense_check_timer, self);
priv->presence_check =
gbinder_timeout_add(priv->presence_check_delay_ms,
gbinder_servicemanager_presense_check_timer, self);
result = G_SOURCE_REMOVE;
} else {
result = G_SOURCE_CONTINUE;
@@ -339,9 +342,9 @@ gbinder_servicemanager_presence_check_start(
{
GBinderServiceManagerPriv* priv = self->priv;
GASSERT(!priv->presence_check_id);
GASSERT(!priv->presence_check);
priv->presence_check_delay_ms = PRESENSE_WAIT_MS_MIN;
priv->presence_check_id = g_timeout_add(PRESENSE_WAIT_MS_MIN,
priv->presence_check = gbinder_timeout_add(PRESENSE_WAIT_MS_MIN,
gbinder_servicemanager_presense_check_timer, self);
}
@@ -391,6 +394,20 @@ gbinder_servicemanager_sleep_ms(
(wait.tv_sec > 0 || wait.tv_nsec > 0));
}
static
void
gbinder_servicemanager_autorelease_cb(
gpointer data)
{
GBinderServiceManager* self = GBINDER_SERVICEMANAGER(data);
GBinderServiceManagerPriv* priv = self->priv;
GSList* list = priv->autorelease;
priv->autorelease_cb = NULL;
priv->autorelease = NULL;
g_slist_free_full(list, g_object_unref);
}
/*==========================================================================*
* Internal interface
*==========================================================================*/
@@ -675,10 +692,15 @@ gbinder_servicemanager_get_service_sync(
if (G_LIKELY(self) && name) {
obj = GBINDER_SERVICEMANAGER_GET_CLASS(self)->get_service
(self, name, status);
if (!self->pool) {
self->pool = gutil_idle_pool_new();
if (obj) {
GBinderServiceManagerPriv* priv = self->priv;
priv->autorelease = g_slist_prepend(priv->autorelease, obj);
if (!priv->autorelease_cb) {
priv->autorelease_cb = gbinder_idle_callback_schedule_new
(gbinder_servicemanager_autorelease_cb, self, NULL);
}
}
gutil_idle_pool_add_object(self->pool, obj);
} else if (status) {
*status = (-EINVAL);
}
@@ -915,12 +937,11 @@ gbinder_servicemanager_finalize(
GBinderServiceManager* self = GBINDER_SERVICEMANAGER(object);
GBinderServiceManagerPriv* priv = self->priv;
if (priv->presence_check_id) {
g_source_remove(priv->presence_check_id);
}
gbinder_timeout_remove(priv->presence_check);
gbinder_remote_object_remove_handler(self->client->remote, priv->death_id);
gbinder_idle_callback_destroy(priv->autorelease_cb);
g_slist_free_full(priv->autorelease, g_object_unref);
g_hash_table_destroy(priv->watch_table);
gutil_idle_pool_destroy(self->pool);
gbinder_client_unref(self->client);
G_OBJECT_CLASS(PARENT_CLASS)->finalize(object);
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright (C) 2018-2019 Jolla Ltd.
* Copyright (C) 2018-2019 Slava Monich <slava.monich@jolla.com>
* Copyright (C) 2018-2020 Jolla Ltd.
* Copyright (C) 2018-2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
@@ -14,8 +14,8 @@
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* 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
@@ -49,7 +49,6 @@ typedef struct gbinder_servicemanager {
GBinderServiceManagerPriv* priv;
const char* dev;
GBinderClient* client;
GUtilIdlePool* pool;
} GBinderServiceManager;
typedef enum gbinder_servicemanager_name_check {

View File

@@ -1,6 +1,6 @@
/*
* Copyright (C) 2018 Jolla Ltd.
* Copyright (C) 2018 Slava Monich <slava.monich@jolla.com>
* Copyright (C) 2018-2020 Jolla Ltd.
* Copyright (C) 2018-2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
@@ -32,6 +32,7 @@
#include "gbinder_servicepoll.h"
#include "gbinder_servicemanager.h"
#include "gbinder_eventloop_p.h"
#include <gutil_strv.h>
@@ -46,7 +47,7 @@ struct gbinder_servicepoll {
GBinderServiceManager* manager;
char** list;
gulong list_id;
guint timer_id;
GBinderEventLoopTimeout* timer;
};
G_DEFINE_TYPE(GBinderServicePoll, gbinder_servicepoll, G_TYPE_OBJECT)
@@ -230,7 +231,7 @@ void
gbinder_servicepoll_init(
GBinderServicePoll* self)
{
self->timer_id = g_timeout_add(gbinder_servicepoll_interval_ms,
self->timer = gbinder_timeout_add(gbinder_servicepoll_interval_ms,
gbinder_servicepoll_timer, self);
}
@@ -241,7 +242,7 @@ gbinder_servicepoll_finalize(
{
GBinderServicePoll* self = GBINDER_SERVICEPOLL(object);
g_source_remove(self->timer_id);
gbinder_timeout_remove(self->timer);
gbinder_servicemanager_cancel(self->manager, self->list_id);
gbinder_servicemanager_unref(self->manager);
g_strfreev(self->list);

View File

@@ -6,6 +6,7 @@ all:
@$(MAKE) -C unit_cleanup $*
@$(MAKE) -C unit_client $*
@$(MAKE) -C unit_driver $*
@$(MAKE) -C unit_eventloop $*
@$(MAKE) -C unit_ipc $*
@$(MAKE) -C unit_local_object $*
@$(MAKE) -C unit_local_reply $*

View File

@@ -8,6 +8,7 @@ unit_buffer \
unit_cleanup \
unit_client \
unit_driver \
unit_eventloop \
unit_ipc \
unit_local_object \
unit_local_reply \

View File

@@ -0,0 +1,5 @@
# -*- Mode: makefile-gmake -*-
EXE = unit_eventloop
include ../common/Makefile

View File

@@ -0,0 +1,262 @@
/*
* Copyright (C) 2020 Jolla Ltd.
* Copyright (C) 2020 Slava Monich <slava.monich@jolla.com>
*
* You may use this file under the terms of BSD license as follows:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders 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 THE COPYRIGHT HOLDERS OR CONTRIBUTORS
* 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 "test_common.h"
#include "gbinder_eventloop_p.h"
static TestOpt test_opt;
static int test_eventloop_timeout_add_called;
static int test_eventloop_callback_new_called;
static int test_eventloop_cleanup_called;
static
gboolean
test_unreached_proc(
gpointer data)
{
g_assert_not_reached();
return G_SOURCE_CONTINUE;
}
/*==========================================================================*
* Test event loop integration
*==========================================================================*/
static
GBinderEventLoopTimeout*
test_eventloop_timeout_add(
guint interval,
GSourceFunc func,
gpointer data)
{
test_eventloop_timeout_add_called++;
return NULL;
}
static
void
test_eventloop_timeout_remove(
GBinderEventLoopTimeout* timeout)
{
g_assert_not_reached();
}
static
GBinderEventLoopCallback*
test_eventloop_callback_new(
GBinderEventLoopCallbackFunc func,
gpointer data,
GDestroyNotify destroy)
{
test_eventloop_callback_new_called++;
return NULL;
}
static
void
test_eventloop_callback_ref(
GBinderEventLoopCallback* cb)
{
g_assert_not_reached();
}
static
void
test_eventloop_callback_unref(
GBinderEventLoopCallback* cb)
{
g_assert_not_reached();
}
static
void
test_eventloop_callback_schedule(
GBinderEventLoopCallback* cb)
{
g_assert_not_reached();
}
static
void
test_eventloop_callback_cancel(
GBinderEventLoopCallback* cb)
{
g_assert_not_reached();
}
static
void
test_eventloop_cleanup(
void)
{
test_eventloop_cleanup_called++;
}
static const GBinderEventLoopIntegration test_eventloop = {
test_eventloop_timeout_add,
test_eventloop_timeout_remove,
test_eventloop_callback_new,
test_eventloop_callback_ref,
test_eventloop_callback_unref,
test_eventloop_callback_schedule,
test_eventloop_callback_cancel,
test_eventloop_cleanup
};
/*==========================================================================*
* replace
*==========================================================================*/
static
void
test_replace(
void)
{
test_eventloop_timeout_add_called = 0;
test_eventloop_callback_new_called = 0;
test_eventloop_cleanup_called = 0;
gbinder_eventloop_set(NULL);
gbinder_eventloop_set(&test_eventloop);
g_assert(!gbinder_timeout_add(0, test_unreached_proc, NULL));
g_assert_cmpint(test_eventloop_timeout_add_called, == ,1);
g_assert(!gbinder_idle_add(test_unreached_proc, NULL));
g_assert_cmpint(test_eventloop_timeout_add_called, == ,2);
gbinder_timeout_remove(NULL);
g_assert(!gbinder_idle_callback_new(NULL, NULL, NULL));
g_assert_cmpint(test_eventloop_callback_new_called, == ,1);
g_assert(!gbinder_idle_callback_ref(NULL));
gbinder_idle_callback_unref(NULL);
gbinder_idle_callback_schedule(NULL);
gbinder_idle_callback_cancel(NULL);
gbinder_eventloop_set(NULL);
g_assert_cmpint(test_eventloop_cleanup_called, == ,1);
}
/*==========================================================================*
* idle
*==========================================================================*/
static
gboolean
test_quit_func(
gpointer data)
{
g_main_loop_quit((GMainLoop*)data);
return G_SOURCE_REMOVE;
}
static
void
test_idle(
void)
{
GMainLoop* loop = g_main_loop_new(NULL, FALSE);
gbinder_eventloop_set(NULL);
g_assert(gbinder_idle_add(test_quit_func, loop));
test_run(&test_opt, loop);
g_main_loop_unref(loop);
}
/*==========================================================================*
* timeout
*==========================================================================*/
static
void
test_timeout(
void)
{
GMainLoop* loop = g_main_loop_new(NULL, FALSE);
gbinder_eventloop_set(NULL);
g_assert(gbinder_timeout_add(10, test_quit_func, loop));
test_run(&test_opt, loop);
g_main_loop_unref(loop);
}
/*==========================================================================*
* callback
*==========================================================================*/
static
void
test_quit_cb(
gpointer data)
{
g_main_loop_quit((GMainLoop*)data);
}
static
void
test_callback(
void)
{
GMainLoop* loop = g_main_loop_new(NULL, FALSE);
GBinderEventLoopCallback* cb;
gbinder_eventloop_set(NULL);
cb = gbinder_idle_callback_new(test_quit_cb, loop, NULL);
gbinder_idle_callback_schedule(cb);
test_run(&test_opt, loop);
gbinder_idle_callback_unref(cb);
g_main_loop_unref(loop);
}
/*==========================================================================*
* Common
*==========================================================================*/
#define TEST_(t) "/eventloop/" t
int main(int argc, char* argv[])
{
g_test_init(&argc, &argv, NULL);
g_test_add_func(TEST_("replace"), test_replace);
g_test_add_func(TEST_("idle"), test_idle);
g_test_add_func(TEST_("timeout"), test_timeout);
g_test_add_func(TEST_("callback"), test_callback);
test_init(&test_opt, argc, argv);
return g_test_run();
}
/*
* Local Variables:
* mode: C
* c-basic-offset: 4
* indent-tabs-mode: nil
* End:
*/