summaryrefslogtreecommitdiff
path: root/osi
diff options
context:
space:
mode:
authorPavlin Radoslavov <pavlin@google.com>2015-12-04 17:36:34 -0800
committerPavlin Radoslavov <pavlin@google.com>2016-01-21 17:49:15 -0800
commit78bcff79e1b1f0efce436b33bdd6da88745bfc8a (patch)
treed6e4add2bd1c0322116052f0a719f1fd6e418917 /osi
parentd2e250824fca5c42b87b3b6f5fa19646ffa2d321 (diff)
downloadandroid-system-bt-78bcff79e1b1f0efce436b33bdd6da88745bfc8a.tar.gz
android-system-bt-78bcff79e1b1f0efce436b33bdd6da88745bfc8a.tar.xz
Refactor the Bluetooth timers
* Updated the alarm API: - Existing API alarm_new() is modified to take an alarm name as an argument. - New API alarm_new_periodic() is used to create a periodic alarm. - Added new API alarm_is_scheduled() to test whether an alarm is scheduled. - Existing API alarm_set_periodic() is removed: a periodic alarm is created by alarm_new_periodic(). - Added new API alarm_set_on_queue() to set an alarm whose callback is executed on a specific queue. - Added new API alarm_register_processing_queue() and alarm_unregister_processing_queue() to register/unregister a queue and the corresponding thread for alarm processing. - Added corresponding unit tests. * Updated the alarm internals: - Added alarm_info_t for collecting alarm-related information and statistics. - Collect and store alarm-related statistics into alarm_info_t per alarm. - Include the alarm-related statistics and info into the native dumpsys output for Bluetooth. - Once an alarm expires, the alarm execution is scheduled for processing on another internal alarm-specific thread, not on the thread that is maintaining the alarms. - Implemented callback execution ordering guarantee among timers on the same thread with exactly same timeout values. * Refactor some of the usage of alarm_set() and simplify the code by using alarm_set_on_queue() instead. * Removed the non_repeating timers wrapper, and use directly the alarm mechanism / API. * Refactored all timer_entry_t timers and replaced them with alarm_t timers: - Replaced the btu_start_timer() / btu_stop_timer() / btu_start_quick_timer() / btu_stop_quick_timer() / btu_oneshot_alarm() mechanism with alarm_set_on_queue() and alarm_cancel() - Removed the whole mechanism around the BTU_TTYPE_* timers. * Fixed a bug when processing the GATT indication confirmation timer expiration (timer tGATT_TCB.conf_timer: b/26610829). * Renamed and/or split misc. timeout functions, fields, and timers * Renamed time-related constants and changed the values from seconds to milliseconds * Replaced timer tAVDT_CCB.timer_entry with three mutually exclusive timers: idle_ccb_timer, ret_ccb_timer, rsp_ccb_timer The reason we are using three timers, is because in the original code function avdt_ccb_chk_timer() used the timer type in its logic: it would stop the timer only if the type is "idle". * Removed btm_ble_timeout() and replaced it with multiple timeout callback functions (per timer) * Fix the actual value of the global constant BT_1SEC_TIMEOUT and rename it to BT_1SEC_TIMEOUT_MS * Removed btu_cb and associated timers and events, because they are never used. * Removed unused timers, functions, struct and declarations that are not used / needed. Bug: 26611369 Bug: 26610829 Change-Id: I812c8c31710a5daefc58b01fcf35c353768f390f
Diffstat (limited to 'osi')
-rw-r--r--osi/Android.mk1
-rw-r--r--osi/BUILD.gn1
-rw-r--r--osi/include/alarm.h95
-rw-r--r--osi/include/non_repeating_timer.h71
-rw-r--r--osi/include/osi.h2
-rw-r--r--osi/include/thread.h2
-rw-r--r--osi/src/alarm.c428
-rw-r--r--osi/src/non_repeating_timer.c74
-rw-r--r--osi/test/alarm_test.cpp148
9 files changed, 556 insertions, 266 deletions
diff --git a/osi/Android.mk b/osi/Android.mk
index a332980..789a17f 100644
--- a/osi/Android.mk
+++ b/osi/Android.mk
@@ -40,7 +40,6 @@ btosiCommonSrc := \
./src/hash_map_utils.c \
./src/list.c \
./src/mutex.c \
- ./src/non_repeating_timer.c \
./src/reactor.c \
./src/ringbuffer.c \
./src/semaphore.c \
diff --git a/osi/BUILD.gn b/osi/BUILD.gn
index 05d386a..28307f3 100644
--- a/osi/BUILD.gn
+++ b/osi/BUILD.gn
@@ -32,7 +32,6 @@ static_library("osi") {
"src/hash_map_utils.c",
"src/list.c",
"src/mutex.c",
- "src/non_repeating_timer.c",
"src/reactor.c",
"src/ringbuffer.c",
"src/semaphore.c",
diff --git a/osi/include/alarm.h b/osi/include/alarm.h
index 13c7d87..9a4210c 100644
--- a/osi/include/alarm.h
+++ b/osi/include/alarm.h
@@ -18,40 +18,93 @@
#pragma once
+#include <stdbool.h>
#include <stdint.h>
typedef struct alarm_t alarm_t;
+typedef struct fixed_queue_t fixed_queue_t;
+typedef struct thread_t thread_t;
typedef uint64_t period_ms_t;
-// Prototype for the callback function.
+// Prototype for the alarm callback function.
typedef void (*alarm_callback_t)(void *data);
-// Creates a new alarm object. The returned object must be freed by calling
-// |alarm_free|. Returns NULL on failure.
-alarm_t *alarm_new(void);
+// Creates a new one-time off alarm object with user-assigned
+// |name|. |name| may not be NULL, and a copy of the string will
+// be stored internally. The value of |name| has no semantic
+// meaning. It is recommended that the name is unique (for
+// better debuggability), but that is not enforced. The returned
+// object must be freed by calling |alarm_free|. Returns NULL on
+// failure.
+alarm_t *alarm_new(const char *name);
-// Frees an alarm object created by |alarm_new|. |alarm| may be NULL. If the
-// alarm is pending, it will be cancelled. It is not safe to call |alarm_free|
-// from inside the callback of |alarm|.
+// Creates a new periodic alarm object with user-assigned |name|.
+// |name| may not be NULL, and a copy of the string will be
+// stored internally. The value of |name| has no semantic
+// meaning. It is recommended that the name is unique (for better
+// debuggability), but that is not enforced. The returned object
+// must be freed by calling |alarm_free|. Returns NULL on
+// failure.
+alarm_t *alarm_new_periodic(const char *name);
+
+// Frees an |alarm| object created by |alarm_new| or
+// |alarm_new_periodic|. |alarm| may be NULL. If the alarm is
+// pending, it will be cancelled first. It is not safe to call
+// |alarm_free| from inside the callback of |alarm|.
void alarm_free(alarm_t *alarm);
-// Sets an alarm to fire |cb| after the given |deadline|. Note that |deadline| is the
-// number of milliseconds relative to the current time. |data| is a context variable
-// for the callback and may be NULL. |cb| will be called back in the context of an
-// unspecified thread (i.e. it will not be called back in the same thread as the caller).
-// |alarm| and |cb| may not be NULL.
-void alarm_set(alarm_t *alarm, period_ms_t deadline, alarm_callback_t cb, void *data);
+// Sets an |alarm| to execute a callback in the future. The |cb|
+// callback is called after the given |interval_ms|, where
+// |interval_ms| is the number of milliseconds relative to the
+// current time. If |alarm| was created with
+// |alarm_new_periodic|, the alarm is scheduled to fire
+// periodically every |interval_ms|, otherwise it is a one time
+// only alarm. A periodic alarm repeats every |interval_ms| until
+// it is cancelled or freed. When the alarm fires, the |cb|
+// callback is called with the context argument |data|:
+//
+// void cb(void *data) {...}
+//
+// The |data| argument may be NULL, but the |cb| callback may not
+// be NULL. All |cb| callbacks scheduled through this call are
+// called within a single (internally created) thread. That
+// thread is not same as the caller’s thread. If two (or more)
+// alarms are set back-to-back with the same |interval_ms|, the
+// callbacks will be called in the order the alarms are set.
+void alarm_set(alarm_t *alarm, period_ms_t interval_ms,
+ alarm_callback_t cb, void *data);
-// Like alarm_set, except the alarm repeats every |period| ms until it is cancelled or
-// freed or |alarm_set| is called to set it non-periodically.
-void alarm_set_periodic(alarm_t *alarm, period_ms_t period, alarm_callback_t cb, void *data);
+// Sets an |alarm| to execute a callback in the future on a
+// specific |queue|. This function is same as |alarm_set| except
+// that the |cb| callback is scheduled for execution in the
+// context of the thread responsible for processing |queue|.
+// Also, the callback execution ordering guarantee exists only
+// among alarms that are scheduled on the same queue. |queue|
+// may not be NULL.
+void alarm_set_on_queue(alarm_t *alarm, period_ms_t interval_ms,
+ alarm_callback_t cb, void *data,
+ fixed_queue_t *queue);
-// This function cancels the |alarm| if it was previously set. When this call
-// returns, the caller has a guarantee that the callback is not in progress and
-// will not be called if it hasn't already been called. This function is idempotent.
+// This function cancels the |alarm| if it was previously set.
+// When this call returns, the caller has a guarantee that the
+// callback is not in progress and will not be called if it
+// hasn't already been called. This function is idempotent.
// |alarm| may not be NULL.
void alarm_cancel(alarm_t *alarm);
+// Tests whether the |alarm| is scheduled.
+// Return true if the |alarm| is scheduled or NULL, otherwise false.
+bool alarm_is_scheduled(const alarm_t *alarm);
+
+// Registers |queue| for processing alarm callbacks on |thread|.
+// |queue| may not be NULL. |thread| may not be NULL.
+void alarm_register_processing_queue(fixed_queue_t *queue, thread_t *thread);
+
+// Unregisters |queue| for processing alarm callbacks on whichever thread
+// it is registered with. |queue| may not be NULL.
+// This function is idempotent.
+void alarm_unregister_processing_queue(fixed_queue_t *queue);
+
// Figure out how much time until next expiration.
// Returns 0 if not armed. |alarm| may not be NULL.
// TODO: Remove this function once PM timers can be re-factored
@@ -61,3 +114,7 @@ period_ms_t alarm_get_remaining_ms(const alarm_t *alarm);
// This function should be called by the OSI module cleanup during
// graceful shutdown.
void alarm_cleanup(void);
+
+// Dump alarm-related statistics and debug info to the |fd| file descriptor.
+// The information is in user-readable text format. The |fd| must be valid.
+void alarm_debug_dump(int fd);
diff --git a/osi/include/non_repeating_timer.h b/osi/include/non_repeating_timer.h
deleted file mode 100644
index 542683c..0000000
--- a/osi/include/non_repeating_timer.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/******************************************************************************
- *
- * Copyright (C) 2014 Google, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- ******************************************************************************/
-
-#pragma once
-
-#include <stdbool.h>
-
-#include "osi/include/alarm.h"
-
-typedef struct non_repeating_timer_t non_repeating_timer_t;
-
-// Creates a new non repeating timer of |duration| with callback |cb|. |cb| will be
-// called back in the context of an upspecified thread, and may not be NULL. |data|
-// is a context variable for the callback and may be NULL. The returned object must
-// be freed by calling |non_repeating_timer_free|. Returns NULL on failure.
-non_repeating_timer_t *non_repeating_timer_new(period_ms_t duration, alarm_callback_t cb, void *data);
-
-// Frees a non repeating timer created by |non_repeating_timer_new|. |timer| may be
-// NULL. If the timer is currently running, it will be cancelled. It is not safe to
-// call |non_repeating_timer_free| from inside the callback of a non repeating timer.
-void non_repeating_timer_free(non_repeating_timer_t *timer);
-
-// Restarts the non repeating timer. If it is currently running, the timer is reset.
-// |timer| may not be NULL.
-void non_repeating_timer_restart(non_repeating_timer_t *timer);
-
-// Restarts the non repeating timer if |condition| is true, otherwise ensures it is
-// not running. |timer| may not be NULL.
-void non_repeating_timer_restart_if(non_repeating_timer_t *timer, bool condition);
-
-// Cancels the non repeating timer if it is currently running. All the semantics of
-// |alarm_cancel| apply here. |timer| may not be NULL.
-void non_repeating_timer_cancel(non_repeating_timer_t *timer);
-
-//
-// TODO: timer_entry_t below should be removed, and its usage everywhere
-// should be replaced by |non_repeating_timer_t| .
-//
-
-// Timer entry callback type
-typedef void (timer_callback_t)(void *p_te);
-typedef void* timer_param_t;
-
-//
-// Define a timer entry
-//
-typedef struct _timer_entry
-{
- timer_callback_t *p_cback;
- int32_t ticks;
- int32_t ticks_initial;
- timer_param_t param;
- timer_param_t data;
- uint16_t event;
- uint8_t in_use;
-} timer_entry_t;
diff --git a/osi/include/osi.h b/osi/include/osi.h
index 73a8db1..78aa68c 100644
--- a/osi/include/osi.h
+++ b/osi/include/osi.h
@@ -25,8 +25,6 @@
#define COMPILE_ASSERT(x) char * DUMMY_PTR = !(x)
#endif // COMPILE_ASSERT
-typedef uint32_t timeout_t;
-
// Macros for safe integer to pointer conversion. In the C language, data is
// commonly cast to opaque pointer containers and back for generic parameter
// passing in callbacks. These macros should be used sparingly in new code
diff --git a/osi/include/thread.h b/osi/include/thread.h
index 9c4068e..b77f726 100644
--- a/osi/include/thread.h
+++ b/osi/include/thread.h
@@ -51,6 +51,7 @@ void thread_join(thread_t *thread);
// does not block unless there are an excessive number of functions posted to
// |thread| that have not been dispatched yet. Neither |thread| nor |func| may
// be NULL. |context| may be NULL.
+// Return true on success, otherwise false.
bool thread_post(thread_t *thread, thread_fn func, void *context);
// Requests |thread| to stop. Only |thread_free| and |thread_name| may be called
@@ -67,6 +68,7 @@ bool thread_set_priority(thread_t *thread, int priority);
// |thread| may not be NULL.
bool thread_is_self(const thread_t *thread);
+// Returns the reactor for the given |thread|. |thread| may not be NULL.
reactor_t *thread_get_reactor(const thread_t *thread);
// Returns the name of the given |thread|. |thread| may not be NULL.
diff --git a/osi/src/alarm.c b/osi/src/alarm.c
index b675e5f..a2328da 100644
--- a/osi/src/alarm.c
+++ b/osi/src/alarm.c
@@ -33,6 +33,7 @@
#include <hardware/bluetooth.h>
#include "osi/include/allocator.h"
+#include "osi/include/fixed_queue.h"
#include "osi/include/list.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
@@ -47,19 +48,42 @@
// TODO(eisenbach): Determine correct thread priority (from parent?/per alarm?)
static const int CALLBACK_THREAD_PRIORITY_HIGH = -19;
+typedef struct {
+ size_t count;
+ period_ms_t total_ms;
+ period_ms_t max_ms;
+} stat_t;
+
+// Alarm-related information and statistics
+typedef struct {
+ const char *name;
+ size_t scheduled_count;
+ size_t canceled_count;
+ size_t rescheduled_count;
+ size_t total_updates;
+ period_ms_t last_update_ms;
+ stat_t callback_execution;
+ stat_t overdue_scheduling;
+ stat_t premature_scheduling;
+} alarm_stats_t;
+
struct alarm_t {
// The lock is held while the callback for this alarm is being executed.
- // It allows us to release the coarse-grained monitor lock while a potentially
- // long-running callback is executing. |alarm_cancel| uses this lock to provide
- // a guarantee to its caller that the callback will not be in progress when it
- // returns.
+ // It allows us to release the coarse-grained monitor lock while a
+ // potentially long-running callback is executing. |alarm_cancel| uses this
+ // lock to provide a guarantee to its caller that the callback will not be
+ // in progress when it returns.
pthread_mutex_t callback_lock;
period_ms_t creation_time;
period_ms_t period;
period_ms_t deadline;
+ period_ms_t prev_deadline; // Previous deadline - used for accounting of
+ // periodic timers
bool is_periodic;
+ fixed_queue_t *queue; // The processing queue to add this alarm to
alarm_callback_t callback;
void *data;
+ alarm_stats_t stats;
};
@@ -72,29 +96,58 @@ static const clockid_t CLOCK_ID = CLOCK_BOOTTIME;
static const clockid_t CLOCK_ID_ALARM = CLOCK_BOOTTIME_ALARM;
// This mutex ensures that the |alarm_set|, |alarm_cancel|, and alarm callback
-// functions execute serially and not concurrently. As a result, this mutex also
-// protects the |alarms| list.
+// functions execute serially and not concurrently. As a result, this mutex
+// also protects the |alarms| list.
static pthread_mutex_t monitor;
static list_t *alarms;
static timer_t timer;
static timer_t wakeup_timer;
static bool timer_set;
-// All alarm callbacks are dispatched from |callback_thread|
-static thread_t *callback_thread;
-static bool callback_thread_active;
+// All alarm callbacks are dispatched from |dispatcher_thread|
+static thread_t *dispatcher_thread;
+static bool dispatcher_thread_active;
static semaphore_t *alarm_expired;
+// Default alarm callback thread and queue
+static thread_t *default_callback_thread;
+static fixed_queue_t *default_callback_queue;
+
+static alarm_t *alarm_new_internal(const char *name, bool is_periodic);
static bool lazy_initialize(void);
static period_ms_t now(void);
-static void alarm_set_internal(alarm_t *alarm, period_ms_t deadline, alarm_callback_t cb, void *data, bool is_periodic);
-static void schedule_next_instance(alarm_t *alarm, bool force_reschedule);
+static void alarm_set_internal(alarm_t *alarm, period_ms_t period,
+ alarm_callback_t cb, void *data,
+ fixed_queue_t *queue);
+static void remove_pending_alarm(alarm_t *alarm);
+static void schedule_next_instance(alarm_t *alarm);
static void reschedule_root_alarm(void);
+static void alarm_queue_ready(fixed_queue_t *queue, void *context);
static void timer_callback(void *data);
static void callback_dispatch(void *context);
static bool timer_create_internal(const clockid_t clock_id, timer_t *timer);
+static void update_scheduling_stats(alarm_stats_t *stats,
+ period_ms_t now_ms,
+ period_ms_t deadline_ms,
+ period_ms_t execution_delta_ms);
+
+static void update_stat(stat_t *stat, period_ms_t delta)
+{
+ if (stat->max_ms < delta)
+ stat->max_ms = delta;
+ stat->total_ms += delta;
+ stat->count++;
+}
-alarm_t *alarm_new(void) {
+alarm_t *alarm_new(const char *name) {
+ return alarm_new_internal(name, false);
+}
+
+alarm_t *alarm_new_periodic(const char *name) {
+ return alarm_new_internal(name, true);
+}
+
+static alarm_t *alarm_new_internal(const char *name, bool is_periodic) {
// Make sure we have a list we can insert alarms into.
if (!alarms && !lazy_initialize()) {
assert(false); // if initialization failed, we should not continue
@@ -114,20 +167,28 @@ alarm_t *alarm_new(void) {
// within the callback function of the alarm.
int error = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
if (error) {
- LOG_ERROR(LOG_TAG, "%s unable to create a recursive mutex: %s", __func__, strerror(error));
+ LOG_ERROR(LOG_TAG, "%s unable to create a recursive mutex: %s",
+ __func__, strerror(error));
goto error;
}
error = pthread_mutex_init(&ret->callback_lock, &attr);
if (error) {
- LOG_ERROR(LOG_TAG, "%s unable to initialize mutex: %s", __func__, strerror(error));
+ LOG_ERROR(LOG_TAG, "%s unable to initialize mutex: %s",
+ __func__, strerror(error));
goto error;
}
+ ret->is_periodic = is_periodic;
+
+ alarm_stats_t *stats = &ret->stats;
+ stats->name = osi_strdup(name);
+ // NOTE: The stats were reset by osi_calloc() above
+
pthread_mutexattr_destroy(&attr);
return ret;
-error:;
+error:
pthread_mutexattr_destroy(&attr);
osi_free(ret);
return NULL;
@@ -139,31 +200,39 @@ void alarm_free(alarm_t *alarm) {
alarm_cancel(alarm);
pthread_mutex_destroy(&alarm->callback_lock);
+ osi_free((void *)alarm->stats.name);
osi_free(alarm);
}
period_ms_t alarm_get_remaining_ms(const alarm_t *alarm) {
assert(alarm != NULL);
period_ms_t remaining_ms = 0;
+ period_ms_t just_now = now();
pthread_mutex_lock(&monitor);
- if (alarm->deadline)
- remaining_ms = alarm->deadline - now();
+ if (alarm->deadline > just_now)
+ remaining_ms = alarm->deadline - just_now;
pthread_mutex_unlock(&monitor);
return remaining_ms;
}
-void alarm_set(alarm_t *alarm, period_ms_t deadline, alarm_callback_t cb, void *data) {
- alarm_set_internal(alarm, deadline, cb, data, false);
+void alarm_set(alarm_t *alarm, period_ms_t interval_ms,
+ alarm_callback_t cb, void *data) {
+ alarm_set_on_queue(alarm, interval_ms, cb, data, default_callback_queue);
}
-void alarm_set_periodic(alarm_t *alarm, period_ms_t period, alarm_callback_t cb, void *data) {
- alarm_set_internal(alarm, period, cb, data, true);
+void alarm_set_on_queue(alarm_t *alarm, period_ms_t interval_ms,
+ alarm_callback_t cb, void *data,
+ fixed_queue_t *queue) {
+ assert(queue != NULL);
+ alarm_set_internal(alarm, interval_ms, cb, data, queue);
}
// Runs in exclusion with alarm_cancel and timer_callback.
-static void alarm_set_internal(alarm_t *alarm, period_ms_t period, alarm_callback_t cb, void *data, bool is_periodic) {
+static void alarm_set_internal(alarm_t *alarm, period_ms_t period,
+ alarm_callback_t cb, void *data,
+ fixed_queue_t *queue) {
assert(alarms != NULL);
assert(alarm != NULL);
assert(cb != NULL);
@@ -171,12 +240,13 @@ static void alarm_set_internal(alarm_t *alarm, period_ms_t period, alarm_callbac
pthread_mutex_lock(&monitor);
alarm->creation_time = now();
- alarm->is_periodic = is_periodic;
alarm->period = period;
+ alarm->queue = queue;
alarm->callback = cb;
alarm->data = data;
- schedule_next_instance(alarm, false);
+ schedule_next_instance(alarm);
+ alarm->stats.scheduled_count++;
pthread_mutex_unlock(&monitor);
}
@@ -189,10 +259,13 @@ void alarm_cancel(alarm_t *alarm) {
bool needs_reschedule = (!list_is_empty(alarms) && list_front(alarms) == alarm);
- list_remove(alarms, alarm);
+ remove_pending_alarm(alarm);
+
alarm->deadline = 0;
+ alarm->prev_deadline = 0;
alarm->callback = NULL;
alarm->data = NULL;
+ alarm->stats.canceled_count++;
if (needs_reschedule)
reschedule_root_alarm();
@@ -204,15 +277,26 @@ void alarm_cancel(alarm_t *alarm) {
pthread_mutex_unlock(&alarm->callback_lock);
}
+bool alarm_is_scheduled(const alarm_t *alarm) {
+ if ((alarms == NULL) || (alarm == NULL))
+ return false;
+ return (alarm->callback != NULL);
+}
+
void alarm_cleanup(void) {
// If lazy_initialize never ran there is nothing else to do
if (!alarms)
return;
- callback_thread_active = false;
+ dispatcher_thread_active = false;
semaphore_post(alarm_expired);
- thread_free(callback_thread);
- callback_thread = NULL;
+ thread_free(dispatcher_thread);
+ dispatcher_thread = NULL;
+
+ fixed_queue_free(default_callback_queue, NULL);
+ default_callback_queue = NULL;
+ thread_free(default_callback_thread);
+ default_callback_thread = NULL;
semaphore_free(alarm_expired);
alarm_expired = NULL;
@@ -253,22 +337,44 @@ static bool lazy_initialize(void) {
goto error;
}
- callback_thread_active = true;
- callback_thread = thread_new("alarm_callbacks");
- if (!callback_thread) {
+ default_callback_thread = thread_new_sized("alarm_default_callbacks",
+ SIZE_MAX);
+ if (default_callback_thread == NULL) {
+ LOG_ERROR(LOG_TAG, "%s unable to create default alarm callbacks thread.",
+ __func__);
+ goto error;
+ }
+ thread_set_priority(default_callback_thread, CALLBACK_THREAD_PRIORITY_HIGH);
+ default_callback_queue = fixed_queue_new(SIZE_MAX);
+ if (default_callback_queue == NULL) {
+ LOG_ERROR(LOG_TAG, "%s unable to create default alarm callbacks queue.",
+ __func__);
+ goto error;
+ }
+ alarm_register_processing_queue(default_callback_queue,
+ default_callback_thread);
+
+ dispatcher_thread_active = true;
+ dispatcher_thread = thread_new("alarm_dispatcher");
+ if (!dispatcher_thread) {
LOG_ERROR(LOG_TAG, "%s unable to create alarm callback thread.", __func__);
goto error;
}
- thread_set_priority(callback_thread, CALLBACK_THREAD_PRIORITY_HIGH);
- thread_post(callback_thread, callback_dispatch, NULL);
+ thread_set_priority(dispatcher_thread, CALLBACK_THREAD_PRIORITY_HIGH);
+ thread_post(dispatcher_thread, callback_dispatch, NULL);
return true;
error:
- thread_free(callback_thread);
- callback_thread = NULL;
+ fixed_queue_free(default_callback_queue, NULL);
+ default_callback_queue = NULL;
+ thread_free(default_callback_thread);
+ default_callback_thread = NULL;
- callback_thread_active = false;
+ thread_free(dispatcher_thread);
+ dispatcher_thread = NULL;
+
+ dispatcher_thread_active = false;
semaphore_free(alarm_expired);
alarm_expired = NULL;
@@ -292,41 +398,58 @@ static period_ms_t now(void) {
struct timespec ts;
if (clock_gettime(CLOCK_ID, &ts) == -1) {
- LOG_ERROR(LOG_TAG, "%s unable to get current time: %s", __func__, strerror(errno));
+ LOG_ERROR(LOG_TAG, "%s unable to get current time: %s",
+ __func__, strerror(errno));
return 0;
}
return (ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL);
}
+// Remove alarm from internal alarm list and the processing queue
+// The caller must hold the |monitor| lock.
+static void remove_pending_alarm(alarm_t *alarm) {
+ list_remove(alarms, alarm);
+ while (fixed_queue_try_remove_from_queue(alarm->queue, alarm) != NULL) {
+ // Remove all repeated alarm instances from the queue.
+ // NOTE: We are defensive here - we shouldn't have repeated alarm instances
+ }
+}
+
// Must be called with monitor held
-static void schedule_next_instance(alarm_t *alarm, bool force_reschedule) {
+static void schedule_next_instance(alarm_t *alarm) {
// If the alarm is currently set and it's at the start of the list,
// we'll need to re-schedule since we've adjusted the earliest deadline.
bool needs_reschedule = (!list_is_empty(alarms) && list_front(alarms) == alarm);
if (alarm->callback)
- list_remove(alarms, alarm);
+ remove_pending_alarm(alarm);
// Calculate the next deadline for this alarm
period_ms_t just_now = now();
- period_ms_t ms_into_period = alarm->is_periodic ? ((just_now - alarm->creation_time) % alarm->period) : 0;
+ period_ms_t ms_into_period = 0;
+ if (alarm->is_periodic)
+ ms_into_period = ((just_now - alarm->creation_time) % alarm->period);
alarm->deadline = just_now + (alarm->period - ms_into_period);
// Add it into the timer list sorted by deadline (earliest deadline first).
- if (list_is_empty(alarms) || ((alarm_t *)list_front(alarms))->deadline >= alarm->deadline)
+ if (list_is_empty(alarms) ||
+ ((alarm_t *)list_front(alarms))->deadline > alarm->deadline) {
list_prepend(alarms, alarm);
- else
+ } else {
for (list_node_t *node = list_begin(alarms); node != list_end(alarms); node = list_next(node)) {
list_node_t *next = list_next(node);
- if (next == list_end(alarms) || ((alarm_t *)list_node(next))->deadline >= alarm->deadline) {
+ if (next == list_end(alarms) || ((alarm_t *)list_node(next))->deadline > alarm->deadline) {
list_insert_after(alarms, node, alarm);
break;
}
}
+ }
// If the new alarm has the earliest deadline, we need to re-evaluate our schedule.
- if (force_reschedule || needs_reschedule || (!list_is_empty(alarms) && list_front(alarms) == alarm))
+ if (needs_reschedule ||
+ (!list_is_empty(alarms) && list_front(alarms) == alarm)) {
reschedule_root_alarm();
+ }
}
// NOTE: must be called with monitor lock.
@@ -355,17 +478,18 @@ static void reschedule_root_alarm(void) {
timer_time.it_value.tv_sec = (next->deadline / 1000);
timer_time.it_value.tv_nsec = (next->deadline % 1000) * 1000000LL;
- // It is entirely unsafe to call timer_settime(2) with a zeroed timerspec for
- // timers with *_ALARM clock IDs. Although the man page states that the timer
- // would be canceled, the current behavior (as of Linux kernel 3.17) is that
- // the callback is issued immediately. The only way to cancel an *_ALARM timer
- // is to delete the timer. But unfortunately, deleting and re-creating a timer
- // is rather expensive; every timer_create(2) spawns a new thread. So we simply
- // set the timer to fire at the largest possible time.
+ // It is entirely unsafe to call timer_settime(2) with a zeroed timerspec
+ // for timers with *_ALARM clock IDs. Although the man page states that the
+ // timer would be canceled, the current behavior (as of Linux kernel 3.17)
+ // is that the callback is issued immediately. The only way to cancel an
+ // *_ALARM timer is to delete the timer. But unfortunately, deleting and
+ // re-creating a timer is rather expensive; every timer_create(2) spawns a
+ // new thread. So we simply set the timer to fire at the largest possible
+ // time.
//
- // If we've reached this code path, we're going to grab a wake lock and wait for
- // the next timer to fire. In that case, there's no reason to have a pending wakeup
- // timer so we simply cancel it.
+ // If we've reached this code path, we're going to grab a wake lock and
+ // wait for the next timer to fire. In that case, there's no reason to
+ // have a pending wakeup timer so we simply cancel it.
struct itimerspec end_of_time;
memset(&end_of_time, 0, sizeof(end_of_time));
end_of_time.it_value.tv_sec = (time_t)(1LL << (sizeof(time_t) * 8 - 2));
@@ -394,36 +518,96 @@ done:
if (timer_settime(timer, TIMER_ABSTIME, &timer_time, NULL) == -1)
LOG_ERROR(LOG_TAG, "%s unable to set timer: %s", __func__, strerror(errno));
- // If next expiration was in the past (e.g. short timer that got context switched)
- // then the timer might have diarmed itself. Detect this case and work around it
- // by manually signalling the |alarm_expired| semaphore.
+ // If next expiration was in the past (e.g. short timer that got context
+ // switched) then the timer might have diarmed itself. Detect this case and
+ // work around it by manually signalling the |alarm_expired| semaphore.
//
- // It is possible that the timer was actually super short (a few milliseconds)
- // and the timer expired normally before we called |timer_gettime|. Worst case,
- // |alarm_expired| is signaled twice for that alarm. Nothing bad should happen in
- // that case though since the callback dispatch function checks to make sure the
- // timer at the head of the list actually expired.
+ // It is possible that the timer was actually super short (a few
+ // milliseconds) and the timer expired normally before we called
+ // |timer_gettime|. Worst case, |alarm_expired| is signaled twice for that
+ // alarm. Nothing bad should happen in that case though since the callback
+ // dispatch function checks to make sure the timer at the head of the list
+ // actually expired.
if (timer_set) {
struct itimerspec time_to_expire;
timer_gettime(timer, &time_to_expire);
- if (time_to_expire.it_value.tv_sec == 0 && time_to_expire.it_value.tv_nsec == 0) {
- LOG_ERROR(LOG_TAG, "%s alarm expiration too close for posix timers, switching to guns", __func__);
+ if (time_to_expire.it_value.tv_sec == 0 &&
+ time_to_expire.it_value.tv_nsec == 0) {
+ LOG_DEBUG(LOG_TAG, "%s alarm expiration too close for posix timers, switching to guns", __func__);
semaphore_post(alarm_expired);
}
}
}
+void alarm_register_processing_queue(fixed_queue_t *queue, thread_t *thread) {
+ assert(queue != NULL);
+ assert(thread != NULL);
+
+ fixed_queue_register_dequeue(queue, thread_get_reactor(thread),
+ alarm_queue_ready, NULL);
+}
+
+void alarm_unregister_processing_queue(fixed_queue_t *queue) {
+ fixed_queue_unregister_dequeue(queue);
+}
+
+static void alarm_queue_ready(fixed_queue_t *queue,
+ UNUSED_ATTR void *context) {
+ assert(queue != NULL);
+
+ pthread_mutex_lock(&monitor);
+ alarm_t *alarm = (alarm_t *)fixed_queue_try_dequeue(queue);
+ if (alarm == NULL) {
+ pthread_mutex_unlock(&monitor);
+ return; // The alarm was probably canceled
+ }
+
+ //
+ // If the alarm is not periodic, we've fully serviced it now, and can reset
+ // some of its internal state. This is useful to distinguish between expired
+ // alarms and active ones.
+ //
+ alarm_callback_t callback = alarm->callback;
+ void *data = alarm->data;
+ period_ms_t deadline = alarm->deadline;
+ if (alarm->is_periodic) {
+ // The periodic alarm has been rescheduled and alarm->deadline has been
+ // updated, hence we need to use the previous deadline.
+ deadline = alarm->prev_deadline;
+ } else {
+ alarm->deadline = 0;
+ alarm->callback = NULL;
+ alarm->data = NULL;
+ }
+
+ pthread_mutex_lock(&alarm->callback_lock);
+ pthread_mutex_unlock(&monitor);
+
+ period_ms_t t0 = now();
+ callback(data);
+ period_ms_t t1 = now();
+
+ // Update the statistics
+ assert(t1 >= t0);
+ period_ms_t delta = t1 - t0;
+ update_scheduling_stats(&alarm->stats, t0, deadline, delta);
+
+ pthread_mutex_unlock(&alarm->callback_lock);
+}
+
// Callback function for wake alarms and our posix timer
static void timer_callback(UNUSED_ATTR void *ptr) {
semaphore_post(alarm_expired);
}
-// Function running on |callback_thread| that dispatches alarm callbacks upon
-// alarm expiration, which is signaled using |alarm_expired|.
+// Function running on |dispatcher_thread| that performs the following:
+// (1) Receives a signal using |alarm_exired| that the alarm has expired
+// (2) Dispatches the alarm callback for processing by the corresponding
+// thread for that alarm.
static void callback_dispatch(UNUSED_ATTR void *context) {
while (true) {
semaphore_wait(alarm_expired);
- if (!callback_thread_active)
+ if (!dispatcher_thread_active)
break;
pthread_mutex_lock(&monitor);
@@ -433,7 +617,8 @@ static void callback_dispatch(UNUSED_ATTR void *context) {
// We're done here if there are no alarms or the alarm at the front is in
// the future. Release the monitor lock and exit right away since there's
// nothing left to do.
- if (list_is_empty(alarms) || (alarm = list_front(alarms))->deadline > now()) {
+ if (list_is_empty(alarms) ||
+ (alarm = list_front(alarms))->deadline > now()) {
reschedule_root_alarm();
pthread_mutex_unlock(&monitor);
continue;
@@ -441,26 +626,17 @@ static void callback_dispatch(UNUSED_ATTR void *context) {
list_remove(alarms, alarm);
- alarm_callback_t callback = alarm->callback;
- void *data = alarm->data;
-
if (alarm->is_periodic) {
- schedule_next_instance(alarm, true);
- } else {
- reschedule_root_alarm();
-
- alarm->deadline = 0;
- alarm->callback = NULL;
- alarm->data = NULL;
+ alarm->prev_deadline = alarm->deadline;
+ schedule_next_instance(alarm);
+ alarm->stats.rescheduled_count++;
}
+ reschedule_root_alarm();
- // Downgrade lock.
- pthread_mutex_lock(&alarm->callback_lock);
- pthread_mutex_unlock(&monitor);
-
- callback(data);
+ // Enqueue the alarm for processing
+ fixed_queue_enqueue(alarm->queue, alarm);
- pthread_mutex_unlock(&alarm->callback_lock);
+ pthread_mutex_unlock(&monitor);
}
LOG_DEBUG(LOG_TAG, "%s Callback thread exited", __func__);
@@ -481,3 +657,87 @@ static bool timer_create_internal(const clockid_t clock_id, timer_t *timer) {
return true;
}
+
+static void update_scheduling_stats(alarm_stats_t *stats,
+ period_ms_t now_ms,
+ period_ms_t deadline_ms,
+ period_ms_t execution_delta_ms)
+{
+ stats->total_updates++;
+ stats->last_update_ms = now_ms;
+
+ update_stat(&stats->callback_execution, execution_delta_ms);
+
+ if (deadline_ms < now_ms) {
+ // Overdue scheduling
+ period_ms_t delta_ms = now_ms - deadline_ms;
+ update_stat(&stats->overdue_scheduling, delta_ms);
+ } else if (deadline_ms > now_ms) {
+ // Premature scheduling
+ period_ms_t delta_ms = deadline_ms - now_ms;
+ update_stat(&stats->premature_scheduling, delta_ms);
+ }
+}
+
+static void dump_stat(int fd, stat_t *stat, const char *description)
+{
+ period_ms_t ave_time_ms;
+
+ ave_time_ms = 0;
+ if (stat->count != 0) {
+ ave_time_ms = stat->total_ms / stat->count;
+ }
+ dprintf(fd, "%s in ms (total/max/ave) : %llu / %llu / %llu\n",
+ description,
+ (unsigned long long)stat->total_ms,
+ (unsigned long long)stat->max_ms,
+ (unsigned long long)ave_time_ms);
+}
+
+void alarm_debug_dump(int fd)
+{
+ dprintf(fd, "\nBluetooth Alarms Statistics:\n");
+
+ pthread_mutex_lock(&monitor);
+
+ if (alarms == NULL) {
+ pthread_mutex_unlock(&monitor);
+ dprintf(fd, " None\n");
+ return;
+ }
+
+ period_ms_t just_now = now();
+
+ dprintf(fd, " Total Alarms: %zu\n\n", list_length(alarms));
+
+ // Dump info for each alarm
+ for (list_node_t *node = list_begin(alarms); node != list_end(alarms);
+ node = list_next(node)) {
+ alarm_t *alarm = (alarm_t *)list_node(node);
+ alarm_stats_t *stats = &alarm->stats;
+
+ dprintf(fd, " Alarm : %s (%s)\n", stats->name,
+ (alarm->is_periodic) ? "PERIODIC" : "SINGLE");
+
+ dprintf(fd, " Action counts (sched/resched/exec/cancel) : %zu / %zu / %zu / %zu\n",
+ stats->scheduled_count, stats->rescheduled_count,
+ stats->callback_execution.count, stats->canceled_count);
+
+ dprintf(fd, " Deviation counts (overdue/premature) : %zu / %zu\n",
+ stats->overdue_scheduling.count,
+ stats->premature_scheduling.count);
+
+ dprintf(fd, " Time in ms (since creation/interval/remaining) : %llu / %llu / %lld\n",
+ (unsigned long long)(just_now - alarm->creation_time),
+ (unsigned long long) alarm->period,
+ (long long)(alarm->deadline - just_now));
+
+ dump_stat(fd, &stats->callback_execution, " Callback execution time");
+ dump_stat(fd, &stats->overdue_scheduling, " Overdue scheduling time");
+ dump_stat(fd, &stats->premature_scheduling,
+ " Premature scheduling time");
+
+ dprintf(fd, "\n");
+ }
+ pthread_mutex_unlock(&monitor);
+}
diff --git a/osi/src/non_repeating_timer.c b/osi/src/non_repeating_timer.c
deleted file mode 100644
index e5e95d4..0000000
--- a/osi/src/non_repeating_timer.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/******************************************************************************
- *
- * Copyright (C) 2014 Google, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- ******************************************************************************/
-
-#include <assert.h>
-
-#include "osi/include/allocator.h"
-#include "osi/include/osi.h"
-#include "osi/include/non_repeating_timer.h"
-
-struct non_repeating_timer_t {
- alarm_t *alarm;
- period_ms_t duration;
- alarm_callback_t callback;
- void *data;
-};
-
-non_repeating_timer_t *non_repeating_timer_new(period_ms_t duration, alarm_callback_t cb, void *data) {
- assert(cb != NULL);
-
- non_repeating_timer_t *ret = osi_calloc(sizeof(non_repeating_timer_t));
-
- ret->alarm = alarm_new();
- if (!ret->alarm)
- goto error;
-
- ret->duration = duration;
- ret->callback = cb;
- ret->data = data;
-
- return ret;
-error:;
- non_repeating_timer_free(ret);
- return NULL;
-}
-
-void non_repeating_timer_free(non_repeating_timer_t *timer) {
- if (!timer)
- return;
-
- alarm_free(timer->alarm);
- osi_free(timer);
-}
-
-void non_repeating_timer_restart(non_repeating_timer_t *timer) {
- non_repeating_timer_restart_if(timer, true);
-}
-
-void non_repeating_timer_restart_if(non_repeating_timer_t *timer, bool condition) {
- assert(timer != NULL);
- if (condition)
- alarm_set(timer->alarm, timer->duration, timer->callback, timer->data);
- else
- non_repeating_timer_cancel(timer);
-}
-
-void non_repeating_timer_cancel(non_repeating_timer_t *timer) {
- assert(timer != NULL);
- alarm_cancel(timer->alarm);
-}
diff --git a/osi/test/alarm_test.cpp b/osi/test/alarm_test.cpp
index 88beaf5..c4a2dca 100644
--- a/osi/test/alarm_test.cpp
+++ b/osi/test/alarm_test.cpp
@@ -22,12 +22,15 @@
extern "C" {
#include "osi/include/alarm.h"
+#include "osi/include/fixed_queue.h"
#include "osi/include/osi.h"
#include "osi/include/semaphore.h"
+#include "osi/include/thread.h"
}
static semaphore_t *semaphore;
static int cb_counter;
+static int cb_misordered_counter;
static const uint64_t EPSILON_MS = 5;
@@ -40,6 +43,7 @@ class AlarmTest : public AlarmTestHarness {
virtual void SetUp() {
AlarmTestHarness::SetUp();
cb_counter = 0;
+ cb_misordered_counter = 0;
semaphore = semaphore_new(0);
}
@@ -55,8 +59,16 @@ static void cb(UNUSED_ATTR void *data) {
semaphore_post(semaphore);
}
+static void ordered_cb(void *data) {
+ int i = PTR_TO_INT(data);
+ if (i != cb_counter)
+ cb_misordered_counter++;
+ ++cb_counter;
+ semaphore_post(semaphore);
+}
+
TEST_F(AlarmTest, test_new_free_simple) {
- alarm_t *alarm = alarm_new();
+ alarm_t *alarm = alarm_new("alarm_test.test_new_free_simple");
ASSERT_TRUE(alarm != NULL);
alarm_free(alarm);
}
@@ -66,25 +78,25 @@ TEST_F(AlarmTest, test_free_null) {
}
TEST_F(AlarmTest, test_simple_cancel) {
- alarm_t *alarm = alarm_new();
+ alarm_t *alarm = alarm_new("alarm_test.test_simple_cancel");
alarm_cancel(alarm);
alarm_free(alarm);
}
TEST_F(AlarmTest, test_cancel) {
- alarm_t *alarm = alarm_new();
+ alarm_t *alarm = alarm_new("alarm_test.test_cancel");
alarm_set(alarm, 10, cb, NULL);
alarm_cancel(alarm);
msleep(10 + EPSILON_MS);
EXPECT_EQ(cb_counter, 0);
- EXPECT_FALSE(WakeLockHeld());;
+ EXPECT_FALSE(WakeLockHeld());
alarm_free(alarm);
}
TEST_F(AlarmTest, test_cancel_idempotent) {
- alarm_t *alarm = alarm_new();
+ alarm_t *alarm = alarm_new("alarm_test.test_cancel_idempotent");
alarm_set(alarm, 10, cb, NULL);
alarm_cancel(alarm);
alarm_cancel(alarm);
@@ -93,7 +105,8 @@ TEST_F(AlarmTest, test_cancel_idempotent) {
}
TEST_F(AlarmTest, test_set_short) {
- alarm_t *alarm = alarm_new();
+ alarm_t *alarm = alarm_new("alarm_test.test_set_short");
+
alarm_set(alarm, 10, cb, NULL);
EXPECT_EQ(cb_counter, 0);
@@ -107,8 +120,28 @@ TEST_F(AlarmTest, test_set_short) {
alarm_free(alarm);
}
+TEST_F(AlarmTest, test_set_short_periodic) {
+ alarm_t *alarm = alarm_new_periodic("alarm_test.test_set_short_periodic");
+
+ alarm_set(alarm, 10, cb, NULL);
+
+ EXPECT_EQ(cb_counter, 0);
+ EXPECT_TRUE(WakeLockHeld());
+
+ for (int i = 1; i <= 10; i++) {
+ semaphore_wait(semaphore);
+
+ EXPECT_GE(cb_counter, i);
+ EXPECT_TRUE(WakeLockHeld());
+ }
+ alarm_cancel(alarm);
+ EXPECT_FALSE(WakeLockHeld());
+
+ alarm_free(alarm);
+}
+
TEST_F(AlarmTest, test_set_long) {
- alarm_t *alarm = alarm_new();
+ alarm_t *alarm = alarm_new("alarm_test.test_set_long");
alarm_set(alarm, TIMER_INTERVAL_FOR_WAKELOCK_IN_MS + EPSILON_MS, cb, NULL);
EXPECT_EQ(cb_counter, 0);
@@ -124,8 +157,8 @@ TEST_F(AlarmTest, test_set_long) {
TEST_F(AlarmTest, test_set_short_short) {
alarm_t *alarm[2] = {
- alarm_new(),
- alarm_new()
+ alarm_new("alarm_test.test_set_short_short_0"),
+ alarm_new("alarm_test.test_set_short_short_1")
};
alarm_set(alarm[0], 10, cb, NULL);
@@ -150,8 +183,8 @@ TEST_F(AlarmTest, test_set_short_short) {
TEST_F(AlarmTest, test_set_short_long) {
alarm_t *alarm[2] = {
- alarm_new(),
- alarm_new()
+ alarm_new("alarm_test.test_set_short_long_0"),
+ alarm_new("alarm_test.test_set_short_long_1")
};
alarm_set(alarm[0], 10, cb, NULL);
@@ -176,8 +209,8 @@ TEST_F(AlarmTest, test_set_short_long) {
TEST_F(AlarmTest, test_set_long_long) {
alarm_t *alarm[2] = {
- alarm_new(),
- alarm_new()
+ alarm_new("alarm_test.test_set_long_long_0"),
+ alarm_new("alarm_test.test_set_long_long_1")
};
alarm_set(alarm[0], TIMER_INTERVAL_FOR_WAKELOCK_IN_MS + EPSILON_MS, cb, NULL);
@@ -200,11 +233,98 @@ TEST_F(AlarmTest, test_set_long_long) {
alarm_free(alarm[1]);
}
+TEST_F(AlarmTest, test_is_scheduled) {
+ alarm_t *alarm = alarm_new("alarm_test.test_is_scheduled");
+
+ EXPECT_FALSE(alarm_is_scheduled((alarm_t *)NULL));
+ EXPECT_FALSE(alarm_is_scheduled(alarm));
+ alarm_set(alarm, TIMER_INTERVAL_FOR_WAKELOCK_IN_MS + EPSILON_MS, cb, NULL);
+ EXPECT_TRUE(alarm_is_scheduled(alarm));
+
+ EXPECT_EQ(cb_counter, 0);
+ EXPECT_FALSE(WakeLockHeld());
+
+ semaphore_wait(semaphore);
+
+ EXPECT_FALSE(alarm_is_scheduled(alarm));
+ EXPECT_EQ(cb_counter, 1);
+ EXPECT_FALSE(WakeLockHeld());
+
+ alarm_free(alarm);
+}
+
+// Test whether the callbacks are invoked in the expected order
+TEST_F(AlarmTest, test_callback_ordering) {
+ alarm_t *alarms[100];
+
+ for (int i = 0; i < 100; i++) {
+ const std::string alarm_name = "alarm_test.test_callback_ordering[" +
+ std::to_string(i) + "]";
+ alarms[i] = alarm_new(alarm_name.c_str());
+ }
+
+ for (int i = 0; i < 100; i++) {
+ alarm_set(alarms[i], 100, ordered_cb, INT_TO_PTR(i));
+ }
+
+ for (int i = 1; i <= 100; i++) {
+ semaphore_wait(semaphore);
+ EXPECT_GE(cb_counter, i);
+ }
+ EXPECT_EQ(cb_counter, 100);
+ EXPECT_EQ(cb_misordered_counter, 0);
+
+ for (int i = 0; i < 100; i++)
+ alarm_free(alarms[i]);
+
+ EXPECT_FALSE(WakeLockHeld());
+}
+
+// Test whether the callbacks are involed in the expected order on a
+// separate queue.
+TEST_F(AlarmTest, test_callback_ordering_on_queue) {
+ alarm_t *alarms[100];
+ fixed_queue_t *queue = fixed_queue_new(SIZE_MAX);
+ thread_t *thread = thread_new("timers.test_callback_ordering_on_queue.thread");
+
+ alarm_register_processing_queue(queue, thread);
+
+ for (int i = 0; i < 100; i++) {
+ const std::string alarm_name =
+ "alarm_test.test_callback_ordering_on_queue[" +
+ std::to_string(i) + "]";
+ alarms[i] = alarm_new(alarm_name.c_str());
+ }
+
+ for (int i = 0; i < 100; i++) {
+ alarm_set_on_queue(alarms[i], 100, ordered_cb, INT_TO_PTR(i), queue);
+ }
+
+ for (int i = 1; i <= 100; i++) {
+ semaphore_wait(semaphore);
+ EXPECT_GE(cb_counter, i);
+ }
+ EXPECT_EQ(cb_counter, 100);
+ EXPECT_EQ(cb_misordered_counter, 0);
+
+ for (int i = 0; i < 100; i++)
+ alarm_free(alarms[i]);
+
+ EXPECT_FALSE(WakeLockHeld());
+
+ alarm_unregister_processing_queue(queue);
+ fixed_queue_free(queue, NULL);
+ thread_free(thread);
+}
+
// Try to catch any race conditions between the timer callback and |alarm_free|.
TEST_F(AlarmTest, test_callback_free_race) {
for (int i = 0; i < 1000; ++i) {
- alarm_t *alarm = alarm_new();
+ const std::string alarm_name = "alarm_test.test_callback_free_race[" +
+ std::to_string(i) + "]";
+ alarm_t *alarm = alarm_new(alarm_name.c_str());
alarm_set(alarm, 0, cb, NULL);
alarm_free(alarm);
}
+ alarm_cleanup();
}