Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e0a3e23f authored by Zach Johnson's avatar Zach Johnson Committed by Andre Eisenbach
Browse files

Add ability to set periodic alarms

Adds alarm_set_periodic so the alarm code can have more
contextual information when rescheduling alarms.

Problem: A2DP would stream for a few seconds and then
stop working.

Cause: The Java garbage collector. Bluedroid reaches out
to javaland to acquire and release the wake lock. Alarm was
always reaching out to get the wake lock when it scheduled a
short timeout. If GC kicked in during that call out to make
sure we have the wake lock, it could take more than 100ms to
get back us. That would screw over the alarm implementation
particularly for small 20ms timers.

So now if the wake lock was already acquired, we don't try to
reacquire it.

Cool. But we still have thrashing. Why? Because the alarm code
doesn't know the alarm is actually being used in a periodic way.

Here's what used to happen:

alarm expires
alarm is removed
reschedule
alarm callback is called
alarm callback sets the alarm again
alarm is added
reschedule

The problem is the first reschedule will get rid of the wake lock
if the next alarm is too far out or doesn't exist, meaning the next
reschedule needs to get the wake lock again.

With the extra periodicity information we can eliminate the
unnecessary intermediate reschedule, meaning no thrashing on the
wake lock. yay!
parent afa8eac4
Loading
Loading
Loading
Loading
+3 −5
Original line number Diff line number Diff line
@@ -1899,11 +1899,10 @@ void btif_a2dp_set_peer_sep(UINT8 sep) {

static void btif_decode_alarm_cb(UNUSED_ATTR void *context) {
  thread_post(worker_thread, btif_media_task_avk_handle_timer, NULL);
  alarm_set(btif_media_cb.decode_alarm, BTIF_SINK_MEDIA_TIME_TICK, btif_decode_alarm_cb, NULL);
}

static void btif_media_task_aa_handle_stop_decoding(void) {
  alarm_cancel(btif_media_cb.decode_alarm);
  alarm_free(btif_media_cb.decode_alarm);
  btif_media_cb.decode_alarm = NULL;
}

@@ -1917,7 +1916,7 @@ static void btif_media_task_aa_handle_start_decoding(void) {
    return;
  }

  alarm_set(btif_media_cb.decode_alarm, BTIF_SINK_MEDIA_TIME_TICK, btif_decode_alarm_cb, NULL);
  alarm_set_periodic(btif_media_cb.decode_alarm, BTIF_SINK_MEDIA_TIME_TICK, btif_decode_alarm_cb, NULL);
}

#if (BTA_AV_SINK_INCLUDED == TRUE)
@@ -2098,7 +2097,6 @@ static void btif_media_task_feeding_state_reset(void)

static void btif_media_task_alarm_cb(UNUSED_ATTR void *context) {
  thread_post(worker_thread, btif_media_task_aa_handle_timer, NULL);
  alarm_set(btif_media_cb.media_alarm, BTIF_MEDIA_TIME_TICK, btif_media_task_alarm_cb, NULL);
}

/*******************************************************************************
@@ -2134,7 +2132,7 @@ static void btif_media_task_aa_start_tx(void)
      return;
    }

    alarm_set(btif_media_cb.media_alarm, BTIF_MEDIA_TIME_TICK, btif_media_task_alarm_cb, NULL);
    alarm_set_periodic(btif_media_cb.media_alarm, BTIF_MEDIA_TIME_TICK, btif_media_task_alarm_cb, NULL);
}

/*******************************************************************************
+4 −0
Original line number Diff line number Diff line
@@ -42,6 +42,10 @@ void alarm_free(alarm_t *alarm);
// |alarm| and |cb| may not be NULL.
void alarm_set(alarm_t *alarm, period_ms_t deadline, 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);

// 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.
+60 −31
Original line number Diff line number Diff line
@@ -39,6 +39,8 @@ struct alarm_t {
  pthread_mutex_t callback_lock;
  period_ms_t deadline;
  alarm_callback_t callback;
  bool periodic;
  period_ms_t period;
  void *data;
};

@@ -62,6 +64,8 @@ static bool timer_set;

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(alarm_t *alarm, period_ms_t deadline);
static void timer_callback(void *data);
static void reschedule(void);

@@ -111,39 +115,28 @@ void alarm_free(alarm_t *alarm) {
  osi_free(alarm);
}

// Runs in exclusion with alarm_cancel and timer_callback.
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_periodic(alarm_t *alarm, period_ms_t period, alarm_callback_t cb, void *data) {
  alarm_set_internal(alarm, period, cb, data, true);
}

// Runs in exclusion with alarm_cancel and timer_callback.
static void alarm_set_internal(alarm_t *alarm, period_ms_t deadline, alarm_callback_t cb, void *data, bool is_periodic) {
  assert(alarms != NULL);
  assert(alarm != NULL);
  assert(cb != NULL);

  pthread_mutex_lock(&monitor);

  // 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);

  alarm->deadline = now() + deadline;
  alarm->periodic = is_periodic;
  alarm->period = deadline;
  alarm->callback = cb;
  alarm->data = data;

  // 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)
    list_prepend(alarms, alarm);
  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) {
        list_insert_after(alarms, node, alarm);
        break;
      }
    }

  // If the new alarm has the earliest deadline, we need to re-evaluate our schedule.
  if (needs_reschedule || (!list_is_empty(alarms) && list_front(alarms) == alarm))
    reschedule();
  schedule(alarm, deadline);

  pthread_mutex_unlock(&monitor);
}
@@ -197,6 +190,33 @@ static period_ms_t now(void) {
  return (ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL);
}

// Must be called with monitor held
static void schedule(alarm_t *alarm, period_ms_t deadline) {
  // 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);

  alarm->deadline = now() + deadline;

  // 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)
    list_prepend(alarms, alarm);
  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) {
        list_insert_after(alarms, node, alarm);
        break;
      }
    }

  // If the new alarm has the earliest deadline, we need to re-evaluate our schedule.
  if (needs_reschedule || (!list_is_empty(alarms) && list_front(alarms) == alarm))
    reschedule();
}

// Warning: this function is called in the context of an unknown thread.
// As a result, it must be thread-safe relative to other operations on
// the alarm list.
@@ -207,11 +227,11 @@ static void timer_callback(void *ptr) {
  pthread_mutex_lock(&monitor);

  bool alarm_valid = list_remove(alarms, alarm);
  reschedule();

  // The alarm was cancelled before we got to it. Release the monitor
  // lock and exit right away since there's nothing left to do.
  if (!alarm_valid) {
    reschedule();
    pthread_mutex_unlock(&monitor);
    return;
  }
@@ -219,9 +239,15 @@ static void timer_callback(void *ptr) {
  alarm_callback_t callback = alarm->callback;
  void *data = alarm->data;

  if (alarm->periodic) {
    schedule(alarm, alarm->period);
  } else {
    reschedule();

    alarm->deadline = 0;
    alarm->callback = NULL;
    alarm->data = NULL;
  }

  // Downgrade lock.
  pthread_mutex_lock(&alarm->callback_lock);
@@ -236,6 +262,7 @@ static void timer_callback(void *ptr) {
static void reschedule(void) {
  assert(alarms != NULL);

  bool timer_was_set = timer_set;
  if (timer_set) {
    timer_delete(timer);
    timer_set = false;
@@ -249,11 +276,13 @@ static void reschedule(void) {
  alarm_t *next = list_front(alarms);
  int64_t next_exp = next->deadline - now();
  if (next_exp < TIMER_INTERVAL_FOR_WAKELOCK_IN_MS) {
    if (!timer_was_set) {
      int status = bt_os_callouts->acquire_wake_lock(WAKE_LOCK_ID);
      if (status != BT_STATUS_SUCCESS) {
        LOG_ERROR("%s unable to acquire wake lock: %d", __func__, status);
        return;
      }
    }

    struct sigevent sigevent;
    memset(&sigevent, 0, sizeof(sigevent));