Align inexact alarms in both time bases; don't pull to wall time

The previous implementation failed to properly distinguish between
trigger times in the RTC vs the ELAPSED time bases.  The pernicious
result of this was that sometimes it would decide to align RTC
alarms based on, say, 0 rather than on the real current time.
This would pull the recurrence into wall-clock alignment, with
serious side effects: in particular, periodic tasks that would hit
external network resources would, because *all* devices would be
pulled into wall-clock alignment, wind up inducing heavy QPS
spikes on the backends.

The new implementation works completely differently.  The basic
goal is the same: try to align inexact alarms to "the same" time,
avoiding extra wakeups / radio spinups / etc.  The way this is done
is to divide the timeline into 15-minute quanta, and drift the start
time of every inexact alarm onto one of these 15-minute quantum
boundaries.  The skew between the RTC and ELAPSED time bases is
taken into effect; RTC alarms are no longer pulled into wall clock
alignment.

Fixes bug 3388961

Change-Id: I2a0460e1f5d0e4036f3402f332b642b557b2fc20
This commit is contained in:
Christopher Tate
2011-02-08 13:39:01 -08:00
parent 5ed9a80571
commit b8849c1e85

View File

@ -63,7 +63,10 @@ class AlarmManagerService extends IAlarmManager.Stub {
private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << AlarmManager.ELAPSED_REALTIME_WAKEUP;
private static final int ELAPSED_REALTIME_MASK = 1 << AlarmManager.ELAPSED_REALTIME;
private static final int TIME_CHANGED_MASK = 1 << 16;
// Alignment quantum for inexact repeating alarms
private static final long QUANTUM = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
private static final String TAG = "AlarmManager";
private static final String ClockReceiver_TAG = "ClockReceiver";
private static final boolean localLOGV = false;
@ -83,17 +86,6 @@ class AlarmManagerService extends IAlarmManager.Stub {
private final ArrayList<Alarm> mElapsedRealtimeAlarms = new ArrayList<Alarm>();
private final IncreasingTimeOrder mIncreasingTimeOrder = new IncreasingTimeOrder();
// slots corresponding with the inexact-repeat interval buckets,
// ordered from shortest to longest
private static final long sInexactSlotIntervals[] = {
AlarmManager.INTERVAL_FIFTEEN_MINUTES,
AlarmManager.INTERVAL_HALF_HOUR,
AlarmManager.INTERVAL_HOUR,
AlarmManager.INTERVAL_HALF_DAY,
AlarmManager.INTERVAL_DAY
};
private long mInexactDeliveryTimes[] = { 0, 0, 0, 0, 0};
private int mDescriptor;
private int mBroadcastRefCount = 0;
private PowerManager.WakeLock mWakeLock;
@ -199,58 +191,40 @@ class AlarmManagerService extends IAlarmManager.Stub {
return;
}
// find the slot in the delivery-times array that we will use
int intervalSlot;
for (intervalSlot = 0; intervalSlot < sInexactSlotIntervals.length; intervalSlot++) {
if (sInexactSlotIntervals[intervalSlot] == interval) {
break;
}
if (interval <= 0) {
Slog.w(TAG, "setInexactRepeating ignored because interval " + interval
+ " is invalid");
return;
}
// Non-bucket intervals just fall back to the less-efficient
// unbucketed recurring alarm implementation
if (intervalSlot >= sInexactSlotIntervals.length) {
// If the requested interval isn't a multiple of 15 minutes, just treat it as exact
if (interval % QUANTUM != 0) {
if (localLOGV) Slog.v(TAG, "Interval " + interval + " not a quantum multiple");
setRepeating(type, triggerAtTime, interval, operation);
return;
}
// Align bucketed alarm deliveries by trying to match
// the shortest-interval bucket already scheduled
long bucketTime = 0;
for (int slot = 0; slot < mInexactDeliveryTimes.length; slot++) {
if (mInexactDeliveryTimes[slot] > 0) {
bucketTime = mInexactDeliveryTimes[slot];
break;
}
}
if (bucketTime == 0) {
// If nothing is scheduled yet, just start at the requested time
bucketTime = triggerAtTime;
// Translate times into the ELAPSED timebase for alignment purposes so that
// alignment never tries to match against wall clock times.
final boolean isRtc = (type == AlarmManager.RTC || type == AlarmManager.RTC_WAKEUP);
final long skew = (isRtc)
? System.currentTimeMillis() - SystemClock.elapsedRealtime()
: 0;
// Slip forward to the next ELAPSED-timebase quantum after the stated time. If
// we're *at* a quantum point, leave it alone.
final long adjustedTriggerTime;
long offset = (triggerAtTime - skew) % QUANTUM;
if (offset != 0) {
adjustedTriggerTime = triggerAtTime - offset + QUANTUM;
} else {
// Align the new alarm with the existing bucketed sequence. To achieve
// alignment, we slide the start time around by min{interval, slot interval}
long adjustment = (interval <= sInexactSlotIntervals[intervalSlot])
? interval : sInexactSlotIntervals[intervalSlot];
// The bucket may have started in the past; adjust
while (bucketTime < triggerAtTime) {
bucketTime += adjustment;
}
// Or the bucket may be set to start more than an interval beyond
// our requested trigger time; pull it back to meet our needs
while (bucketTime > triggerAtTime + adjustment) {
bucketTime -= adjustment;
}
adjustedTriggerTime = triggerAtTime;
}
// Remember where this bucket started (reducing the amount of later
// fixup required) and set the alarm with the new, bucketed start time.
if (localLOGV) Slog.v(TAG, "setInexactRepeating: interval=" + interval
+ " bucketTime=" + bucketTime);
mInexactDeliveryTimes[intervalSlot] = bucketTime;
setRepeating(type, bucketTime, interval, operation);
// Set the alarm based on the quantum-aligned start time
if (localLOGV) Slog.v(TAG, "setInexactRepeating: type=" + type + " interval=" + interval
+ " trigger=" + adjustedTriggerTime + " orig=" + triggerAtTime);
setRepeating(type, adjustedTriggerTime, interval, operation);
}
public void setTime(long millis) {