Merge "Allow different explicit health and package expiry timeouts" into qt-dev
am: 0a94974b67
Change-Id: I16ebf4a0e917a2c66a6202a95ab875e38c3dda39
This commit is contained in:
@@ -77,6 +77,7 @@ public class PackageWatchdog {
|
|||||||
private static final String ATTR_VERSION = "version";
|
private static final String ATTR_VERSION = "version";
|
||||||
private static final String ATTR_NAME = "name";
|
private static final String ATTR_NAME = "name";
|
||||||
private static final String ATTR_DURATION = "duration";
|
private static final String ATTR_DURATION = "duration";
|
||||||
|
private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
|
||||||
private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
|
private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
|
||||||
|
|
||||||
private static PackageWatchdog sPackageWatchdog;
|
private static PackageWatchdog sPackageWatchdog;
|
||||||
@@ -95,20 +96,22 @@ public class PackageWatchdog {
|
|||||||
private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
|
private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
|
||||||
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
|
// File containing the XML data of monitored packages /data/system/package-watchdog.xml
|
||||||
private final AtomicFile mPolicyFile;
|
private final AtomicFile mPolicyFile;
|
||||||
// Runnable to prune monitored packages that have expired
|
|
||||||
private final Runnable mPackageCleanup;
|
|
||||||
private final ExplicitHealthCheckController mHealthCheckController;
|
private final ExplicitHealthCheckController mHealthCheckController;
|
||||||
// Flag to control whether explicit health checks are supported or not
|
// Flag to control whether explicit health checks are supported or not
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private boolean mIsHealthCheckEnabled = true;
|
private boolean mIsHealthCheckEnabled = true;
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private boolean mIsPackagesReady;
|
private boolean mIsPackagesReady;
|
||||||
// Last SystemClock#uptimeMillis a package clean up was executed.
|
// SystemClock#uptimeMillis when we last executed #pruneObservers.
|
||||||
// 0 if mPackageCleanup not running.
|
// 0 if no prune is scheduled.
|
||||||
private long mUptimeAtLastRescheduleMs;
|
@GuardedBy("mLock")
|
||||||
// Duration a package cleanup was last scheduled for.
|
private long mUptimeAtLastPruneMs;
|
||||||
// 0 if mPackageCleanup not running.
|
// Duration in millis that the last prune was scheduled for.
|
||||||
private long mDurationAtLastReschedule;
|
// Used along with #mUptimeAtLastPruneMs after scheduling a prune to determine the remaining
|
||||||
|
// duration before #pruneObservers will be executed.
|
||||||
|
// 0 if no prune is scheduled.
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private long mDurationAtLastPrune;
|
||||||
|
|
||||||
private PackageWatchdog(Context context) {
|
private PackageWatchdog(Context context) {
|
||||||
// Needs to be constructed inline
|
// Needs to be constructed inline
|
||||||
@@ -129,7 +132,6 @@ public class PackageWatchdog {
|
|||||||
mPolicyFile = policyFile;
|
mPolicyFile = policyFile;
|
||||||
mShortTaskHandler = shortTaskHandler;
|
mShortTaskHandler = shortTaskHandler;
|
||||||
mLongTaskHandler = longTaskHandler;
|
mLongTaskHandler = longTaskHandler;
|
||||||
mPackageCleanup = this::rescheduleCleanup;
|
|
||||||
mHealthCheckController = controller;
|
mHealthCheckController = controller;
|
||||||
loadFromFile();
|
loadFromFile();
|
||||||
}
|
}
|
||||||
@@ -171,9 +173,9 @@ public class PackageWatchdog {
|
|||||||
if (internalObserver != null) {
|
if (internalObserver != null) {
|
||||||
internalObserver.mRegisteredObserver = observer;
|
internalObserver.mRegisteredObserver = observer;
|
||||||
}
|
}
|
||||||
if (mDurationAtLastReschedule == 0) {
|
if (mDurationAtLastPrune == 0) {
|
||||||
// Nothing running, schedule
|
// Nothing running, prune
|
||||||
rescheduleCleanup();
|
pruneAndSchedule();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,6 +210,7 @@ public class PackageWatchdog {
|
|||||||
|
|
||||||
List<MonitoredPackage> packages = new ArrayList<>();
|
List<MonitoredPackage> packages = new ArrayList<>();
|
||||||
for (int i = 0; i < packageNames.size(); i++) {
|
for (int i = 0; i < packageNames.size(); i++) {
|
||||||
|
// Health checks not available yet so health check state will start INACTIVE
|
||||||
packages.add(new MonitoredPackage(packageNames.get(i), durationMs, false));
|
packages.add(new MonitoredPackage(packageNames.get(i), durationMs, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,9 +228,9 @@ public class PackageWatchdog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
registerHealthObserver(observer);
|
registerHealthObserver(observer);
|
||||||
// Always reschedule because we may need to expire packages
|
// Always prune because we may have received packges requiring an earlier
|
||||||
// earlier than we are already scheduled for
|
// schedule than we are currently scheduled for.
|
||||||
rescheduleCleanup();
|
pruneAndSchedule();
|
||||||
Slog.i(TAG, "Syncing health check requests, observing packages " + packageNames);
|
Slog.i(TAG, "Syncing health check requests, observing packages " + packageNames);
|
||||||
syncRequestsAsync();
|
syncRequestsAsync();
|
||||||
saveToFileAsync();
|
saveToFileAsync();
|
||||||
@@ -312,17 +315,20 @@ public class PackageWatchdog {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(b/120598832): Optimize write? Maybe only write a separate smaller file?
|
// TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
|
||||||
|
// avoid holding lock?
|
||||||
// This currently adds about 7ms extra to shutdown thread
|
// This currently adds about 7ms extra to shutdown thread
|
||||||
/** Writes the package information to file during shutdown. */
|
/** Writes the package information to file during shutdown. */
|
||||||
public void writeNow() {
|
public void writeNow() {
|
||||||
|
synchronized (mLock) {
|
||||||
if (!mAllObservers.isEmpty()) {
|
if (!mAllObservers.isEmpty()) {
|
||||||
mLongTaskHandler.removeCallbacks(this::saveToFile);
|
mLongTaskHandler.removeCallbacks(this::saveToFile);
|
||||||
pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastRescheduleMs);
|
pruneObservers(SystemClock.uptimeMillis() - mUptimeAtLastPruneMs);
|
||||||
saveToFile();
|
saveToFile();
|
||||||
Slog.i(TAG, "Last write to update package durations");
|
Slog.i(TAG, "Last write to update package durations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(b/120598832): Set depending on DeviceConfig flag
|
// TODO(b/120598832): Set depending on DeviceConfig flag
|
||||||
/**
|
/**
|
||||||
@@ -450,9 +456,10 @@ public class PackageWatchdog {
|
|||||||
|
|
||||||
private void onSupportedPackages(List<String> supportedPackages) {
|
private void onSupportedPackages(List<String> supportedPackages) {
|
||||||
boolean shouldUpdateFile = false;
|
boolean shouldUpdateFile = false;
|
||||||
|
boolean shouldPrune = false;
|
||||||
|
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
Slog.i(TAG, "Received supported packages " + supportedPackages);
|
Slog.d(TAG, "Received supported packages " + supportedPackages);
|
||||||
Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
|
Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
|
||||||
while (oit.hasNext()) {
|
while (oit.hasNext()) {
|
||||||
ObserverInternal observer = oit.next();
|
ObserverInternal observer = oit.next();
|
||||||
@@ -461,12 +468,31 @@ public class PackageWatchdog {
|
|||||||
while (pit.hasNext()) {
|
while (pit.hasNext()) {
|
||||||
MonitoredPackage monitoredPackage = pit.next();
|
MonitoredPackage monitoredPackage = pit.next();
|
||||||
String packageName = monitoredPackage.mName;
|
String packageName = monitoredPackage.mName;
|
||||||
if (!monitoredPackage.mHasPassedHealthCheck
|
int healthCheckState = monitoredPackage.getHealthCheckState();
|
||||||
&& !supportedPackages.contains(packageName)) {
|
|
||||||
// Hasn't passed health check but health check is not supported
|
if (healthCheckState != MonitoredPackage.STATE_PASSED) {
|
||||||
Slog.i(TAG, packageName + " does not support health checks, passing");
|
// Have to update file, we will either transition state or reduce
|
||||||
|
// health check duration
|
||||||
shouldUpdateFile = true;
|
shouldUpdateFile = true;
|
||||||
|
|
||||||
|
if (supportedPackages.contains(packageName)) {
|
||||||
|
// Supports health check, transition to ACTIVE if not already.
|
||||||
|
// We need to prune packages earlier than already scheduled.
|
||||||
|
shouldPrune = true;
|
||||||
|
|
||||||
|
// TODO: Get healthCheckDuration from supportedPackages
|
||||||
|
long healthCheckDuration = monitoredPackage.mDurationMs;
|
||||||
|
monitoredPackage.mHealthCheckDurationMs = Math.min(healthCheckDuration,
|
||||||
|
monitoredPackage.mDurationMs);
|
||||||
|
Slog.i(TAG, packageName + " health check state is now: ACTIVE("
|
||||||
|
+ monitoredPackage.mHealthCheckDurationMs + "ms)");
|
||||||
|
} else {
|
||||||
|
// Does not support health check, transistion to PASSED
|
||||||
monitoredPackage.mHasPassedHealthCheck = true;
|
monitoredPackage.mHasPassedHealthCheck = true;
|
||||||
|
Slog.i(TAG, packageName + " health check state is now: PASSED");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Slog.i(TAG, packageName + " does not support health check, state: PASSED");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,6 +501,9 @@ public class PackageWatchdog {
|
|||||||
if (shouldUpdateFile) {
|
if (shouldUpdateFile) {
|
||||||
saveToFileAsync();
|
saveToFileAsync();
|
||||||
}
|
}
|
||||||
|
if (shouldPrune) {
|
||||||
|
pruneAndSchedule();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getPackagesPendingHealthChecksLocked() {
|
private Set<String> getPackagesPendingHealthChecksLocked() {
|
||||||
@@ -496,59 +525,64 @@ public class PackageWatchdog {
|
|||||||
return packages;
|
return packages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reschedules handler to prune expired packages from observers. */
|
/** Executes {@link #pruneObservers} and schedules the next execution. */
|
||||||
private void rescheduleCleanup() {
|
private void pruneAndSchedule() {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
long nextDurationToScheduleMs = getEarliestPackageExpiryLocked();
|
long nextDurationToScheduleMs = getNextPruneScheduleMillisLocked();
|
||||||
if (nextDurationToScheduleMs == Long.MAX_VALUE) {
|
if (nextDurationToScheduleMs == Long.MAX_VALUE) {
|
||||||
Slog.i(TAG, "No monitored packages, ending package cleanup");
|
Slog.i(TAG, "No monitored packages, ending prune");
|
||||||
mDurationAtLastReschedule = 0;
|
mDurationAtLastPrune = 0;
|
||||||
mUptimeAtLastRescheduleMs = 0;
|
mUptimeAtLastPruneMs = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long uptimeMs = SystemClock.uptimeMillis();
|
long uptimeMs = SystemClock.uptimeMillis();
|
||||||
// O if mPackageCleanup not running
|
// O if not running
|
||||||
long elapsedDurationMs = mUptimeAtLastRescheduleMs == 0
|
long elapsedDurationMs = mUptimeAtLastPruneMs == 0
|
||||||
? 0 : uptimeMs - mUptimeAtLastRescheduleMs;
|
? 0 : uptimeMs - mUptimeAtLastPruneMs;
|
||||||
// Less than O if mPackageCleanup unexpectedly didn't run yet even though
|
// Less than O if unexpectedly didn't run yet even though
|
||||||
// and we are past the last duration scheduled to run
|
// we are past the last duration scheduled to run
|
||||||
long remainingDurationMs = mDurationAtLastReschedule - elapsedDurationMs;
|
long remainingDurationMs = mDurationAtLastPrune - elapsedDurationMs;
|
||||||
if (mUptimeAtLastRescheduleMs == 0
|
if (mUptimeAtLastPruneMs == 0
|
||||||
|| remainingDurationMs <= 0
|
|| remainingDurationMs <= 0
|
||||||
|| nextDurationToScheduleMs < remainingDurationMs) {
|
|| nextDurationToScheduleMs < remainingDurationMs) {
|
||||||
// First schedule or an earlier reschedule
|
// First schedule or an earlier reschedule
|
||||||
pruneObservers(elapsedDurationMs);
|
pruneObservers(elapsedDurationMs);
|
||||||
mShortTaskHandler.removeCallbacks(mPackageCleanup);
|
// We don't use Handler#hasCallbacks because we want to update the schedule delay
|
||||||
mShortTaskHandler.postDelayed(mPackageCleanup, nextDurationToScheduleMs);
|
mShortTaskHandler.removeCallbacks(this::pruneAndSchedule);
|
||||||
mDurationAtLastReschedule = nextDurationToScheduleMs;
|
mShortTaskHandler.postDelayed(this::pruneAndSchedule, nextDurationToScheduleMs);
|
||||||
mUptimeAtLastRescheduleMs = uptimeMs;
|
mDurationAtLastPrune = nextDurationToScheduleMs;
|
||||||
|
mUptimeAtLastPruneMs = uptimeMs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the earliest time a package should expire.
|
* Returns the next time in millis to schedule a prune.
|
||||||
|
*
|
||||||
* @returns Long#MAX_VALUE if there are no observed packages.
|
* @returns Long#MAX_VALUE if there are no observed packages.
|
||||||
*/
|
*/
|
||||||
private long getEarliestPackageExpiryLocked() {
|
private long getNextPruneScheduleMillisLocked() {
|
||||||
long shortestDurationMs = Long.MAX_VALUE;
|
long shortestDurationMs = Long.MAX_VALUE;
|
||||||
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
|
for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
|
||||||
ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
|
ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex).mPackages;
|
||||||
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
|
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
|
||||||
long duration = packages.valueAt(pIndex).mDurationMs;
|
MonitoredPackage mp = packages.valueAt(pIndex);
|
||||||
|
long duration = Math.min(mp.mDurationMs, mp.mHealthCheckDurationMs);
|
||||||
if (duration < shortestDurationMs) {
|
if (duration < shortestDurationMs) {
|
||||||
shortestDurationMs = duration;
|
shortestDurationMs = duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Slog.v(TAG, "Earliest package time is " + shortestDurationMs);
|
Slog.i(TAG, "Next prune will be scheduled in " + shortestDurationMs + "ms");
|
||||||
|
|
||||||
return shortestDurationMs;
|
return shortestDurationMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes {@code elapsedMs} milliseconds from all durations on monitored packages.
|
* Removes {@code elapsedMs} milliseconds from all durations on monitored packages.
|
||||||
* Discards expired packages and discards observers without any packages.
|
*
|
||||||
|
* <p> Prunes all observers with {@link ObserverInternal#prunePackages} and discards observers
|
||||||
|
* without any packages left.
|
||||||
*/
|
*/
|
||||||
private void pruneObservers(long elapsedMs) {
|
private void pruneObservers(long elapsedMs) {
|
||||||
if (elapsedMs == 0) {
|
if (elapsedMs == 0) {
|
||||||
@@ -559,8 +593,8 @@ public class PackageWatchdog {
|
|||||||
Iterator<ObserverInternal> it = mAllObservers.values().iterator();
|
Iterator<ObserverInternal> it = mAllObservers.values().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
ObserverInternal observer = it.next();
|
ObserverInternal observer = it.next();
|
||||||
List<MonitoredPackage> failedPackages =
|
Set<MonitoredPackage> failedPackages =
|
||||||
observer.updateMonitoringDurations(elapsedMs);
|
observer.prunePackages(elapsedMs);
|
||||||
if (!failedPackages.isEmpty()) {
|
if (!failedPackages.isEmpty()) {
|
||||||
onHealthCheckFailed(observer, failedPackages);
|
onHealthCheckFailed(observer, failedPackages);
|
||||||
}
|
}
|
||||||
@@ -570,32 +604,34 @@ public class PackageWatchdog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Slog.i(TAG, "Syncing health check requests pruned packages");
|
Slog.i(TAG, "Syncing health check requests, pruned observers");
|
||||||
syncRequestsAsync();
|
syncRequestsAsync();
|
||||||
saveToFileAsync();
|
saveToFileAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onHealthCheckFailed(ObserverInternal observer,
|
private void onHealthCheckFailed(ObserverInternal observer,
|
||||||
List<MonitoredPackage> failedPackages) {
|
Set<MonitoredPackage> failedPackages) {
|
||||||
mLongTaskHandler.post(() -> {
|
mLongTaskHandler.post(() -> {
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
|
PackageHealthObserver registeredObserver = observer.mRegisteredObserver;
|
||||||
if (registeredObserver != null) {
|
if (registeredObserver != null) {
|
||||||
PackageManager pm = mContext.getPackageManager();
|
PackageManager pm = mContext.getPackageManager();
|
||||||
for (int i = 0; i < failedPackages.size(); i++) {
|
Iterator<MonitoredPackage> it = failedPackages.iterator();
|
||||||
String packageName = failedPackages.get(i).mName;
|
while (it.hasNext()) {
|
||||||
|
String failedPackage = it.next().mName;
|
||||||
long versionCode = 0;
|
long versionCode = 0;
|
||||||
Slog.i(TAG, "Explicit health check failed for package " + packageName);
|
Slog.i(TAG, "Explicit health check failed for package " + failedPackage);
|
||||||
try {
|
try {
|
||||||
versionCode = pm.getPackageInfo(
|
versionCode = pm.getPackageInfo(
|
||||||
packageName, 0 /* flags */).getLongVersionCode();
|
failedPackage, 0 /* flags */).getLongVersionCode();
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
Slog.w(TAG, "Explicit health check failed but could not find package "
|
Slog.w(TAG, "Explicit health check failed but could not find package "
|
||||||
+ packageName);
|
+ failedPackage);
|
||||||
// TODO(b/120598832): Skip. We only continue to pass tests for now since
|
// TODO(b/120598832): Skip. We only continue to pass tests for now since
|
||||||
// the tests don't install any packages
|
// the tests don't install any packages
|
||||||
}
|
}
|
||||||
registeredObserver.execute(new VersionedPackage(packageName, versionCode));
|
registeredObserver.execute(
|
||||||
|
new VersionedPackage(failedPackage, versionCode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,34 +706,38 @@ public class PackageWatchdog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void saveToFileAsync() {
|
private void saveToFileAsync() {
|
||||||
// TODO(b/120598832): Use Handler#hasCallbacks instead of removing and posting
|
if (!mLongTaskHandler.hasCallbacks(this::saveToFile)) {
|
||||||
mLongTaskHandler.removeCallbacks(this::saveToFile);
|
|
||||||
mLongTaskHandler.post(this::saveToFile);
|
mLongTaskHandler.post(this::saveToFile);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an observer monitoring a set of packages along with the failure thresholds for
|
* Represents an observer monitoring a set of packages along with the failure thresholds for
|
||||||
* each package.
|
* each package.
|
||||||
|
*
|
||||||
|
* <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
|
||||||
|
* instances of this class.
|
||||||
*/
|
*/
|
||||||
static class ObserverInternal {
|
//TODO(b/120598832): Remove 'm' from non-private fields
|
||||||
|
private static class ObserverInternal {
|
||||||
public final String mName;
|
public final String mName;
|
||||||
//TODO(b/120598832): Add getter for mPackages
|
//TODO(b/120598832): Add getter for mPackages
|
||||||
public final ArrayMap<String, MonitoredPackage> mPackages;
|
@GuardedBy("mLock")
|
||||||
|
public final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@GuardedBy("mLock")
|
||||||
public PackageHealthObserver mRegisteredObserver;
|
public PackageHealthObserver mRegisteredObserver;
|
||||||
|
|
||||||
ObserverInternal(String name, List<MonitoredPackage> packages) {
|
ObserverInternal(String name, List<MonitoredPackage> packages) {
|
||||||
mName = name;
|
mName = name;
|
||||||
mPackages = new ArrayMap<>();
|
|
||||||
updatePackages(packages);
|
updatePackages(packages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes important details to file. Doesn't persist any package failure thresholds.
|
* Writes important {@link MonitoredPackage} details for this observer to file.
|
||||||
*
|
* Does not persist any package failure thresholds.
|
||||||
* <p>Note that this method is <b>not</b> thread safe. It should only be called from
|
|
||||||
* #saveToFile which runs on a single threaded handler.
|
|
||||||
*/
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
public boolean write(XmlSerializer out) {
|
public boolean write(XmlSerializer out) {
|
||||||
try {
|
try {
|
||||||
out.startTag(null, TAG_OBSERVER);
|
out.startTag(null, TAG_OBSERVER);
|
||||||
@@ -707,6 +747,8 @@ public class PackageWatchdog {
|
|||||||
out.startTag(null, TAG_PACKAGE);
|
out.startTag(null, TAG_PACKAGE);
|
||||||
out.attribute(null, ATTR_NAME, p.mName);
|
out.attribute(null, ATTR_NAME, p.mName);
|
||||||
out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs));
|
out.attribute(null, ATTR_DURATION, String.valueOf(p.mDurationMs));
|
||||||
|
out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
|
||||||
|
String.valueOf(p.mHealthCheckDurationMs));
|
||||||
out.attribute(null, ATTR_PASSED_HEALTH_CHECK,
|
out.attribute(null, ATTR_PASSED_HEALTH_CHECK,
|
||||||
String.valueOf(p.mHasPassedHealthCheck));
|
String.valueOf(p.mHasPassedHealthCheck));
|
||||||
out.endTag(null, TAG_PACKAGE);
|
out.endTag(null, TAG_PACKAGE);
|
||||||
@@ -719,57 +761,69 @@ public class PackageWatchdog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
public void updatePackages(List<MonitoredPackage> packages) {
|
public void updatePackages(List<MonitoredPackage> packages) {
|
||||||
synchronized (mName) {
|
|
||||||
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
|
for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
|
||||||
MonitoredPackage p = packages.get(pIndex);
|
MonitoredPackage p = packages.get(pIndex);
|
||||||
mPackages.put(p.mName, p);
|
mPackages.put(p.mName, p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces the monitoring durations of all packages observed by this observer by
|
* Reduces the monitoring durations of all packages observed by this observer by
|
||||||
* {@code elapsedMs}. If any duration is less than 0, the package is removed from
|
* {@code elapsedMs}. If any duration is less than 0, the package is removed from
|
||||||
* observation.
|
* observation. If any health check duration is less than 0, the health check result
|
||||||
|
* is evaluated.
|
||||||
*
|
*
|
||||||
* @returns a {@link List} of packages that were removed from the observer without explicit
|
* @returns a {@link Set} of packages that were removed from the observer without explicit
|
||||||
* health check passing, or an empty list if no package expired for which an explicit health
|
* health check passing, or an empty list if no package expired for which an explicit health
|
||||||
* check was still pending
|
* check was still pending
|
||||||
*/
|
*/
|
||||||
public List<MonitoredPackage> updateMonitoringDurations(long elapsedMs) {
|
@GuardedBy("mLock")
|
||||||
List<MonitoredPackage> removedPackages = new ArrayList<>();
|
private Set<MonitoredPackage> prunePackages(long elapsedMs) {
|
||||||
synchronized (mName) {
|
Set<MonitoredPackage> failedPackages = new ArraySet<>();
|
||||||
Iterator<MonitoredPackage> it = mPackages.values().iterator();
|
Iterator<MonitoredPackage> it = mPackages.values().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
MonitoredPackage p = it.next();
|
MonitoredPackage p = it.next();
|
||||||
long newDuration = p.mDurationMs - elapsedMs;
|
int healthCheckState = p.getHealthCheckState();
|
||||||
if (newDuration > 0) {
|
|
||||||
p.mDurationMs = newDuration;
|
// Handle health check timeouts
|
||||||
} else {
|
if (healthCheckState == MonitoredPackage.STATE_ACTIVE) {
|
||||||
if (!p.mHasPassedHealthCheck) {
|
// Only reduce duration if state is active
|
||||||
removedPackages.add(p);
|
p.mHealthCheckDurationMs -= elapsedMs;
|
||||||
|
// Check duration after reducing duration
|
||||||
|
if (p.mHealthCheckDurationMs <= 0) {
|
||||||
|
failedPackages.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle package expiry
|
||||||
|
p.mDurationMs -= elapsedMs;
|
||||||
|
// Check duration after reducing duration
|
||||||
|
if (p.mDurationMs <= 0) {
|
||||||
|
if (healthCheckState == MonitoredPackage.STATE_INACTIVE) {
|
||||||
|
Slog.w(TAG, "Package " + p.mName
|
||||||
|
+ " expiring without starting health check, failing");
|
||||||
|
failedPackages.add(p);
|
||||||
}
|
}
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removedPackages;
|
return failedPackages;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments failure counts of {@code packageName}.
|
* Increments failure counts of {@code packageName}.
|
||||||
* @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
|
* @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
public boolean onPackageFailure(String packageName) {
|
public boolean onPackageFailure(String packageName) {
|
||||||
synchronized (mName) {
|
|
||||||
MonitoredPackage p = mPackages.get(packageName);
|
MonitoredPackage p = mPackages.get(packageName);
|
||||||
if (p != null) {
|
if (p != null) {
|
||||||
return p.onFailure();
|
return p.onFailure();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns one ObserverInternal from the {@code parser} and advances its state.
|
* Returns one ObserverInternal from the {@code parser} and advances its state.
|
||||||
@@ -796,11 +850,14 @@ public class PackageWatchdog {
|
|||||||
String packageName = parser.getAttributeValue(null, ATTR_NAME);
|
String packageName = parser.getAttributeValue(null, ATTR_NAME);
|
||||||
long duration = Long.parseLong(
|
long duration = Long.parseLong(
|
||||||
parser.getAttributeValue(null, ATTR_DURATION));
|
parser.getAttributeValue(null, ATTR_DURATION));
|
||||||
|
long healthCheckDuration = Long.parseLong(
|
||||||
|
parser.getAttributeValue(null,
|
||||||
|
ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
|
||||||
boolean hasPassedHealthCheck = Boolean.parseBoolean(
|
boolean hasPassedHealthCheck = Boolean.parseBoolean(
|
||||||
parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
|
parser.getAttributeValue(null, ATTR_PASSED_HEALTH_CHECK));
|
||||||
if (!TextUtils.isEmpty(packageName)) {
|
if (!TextUtils.isEmpty(packageName)) {
|
||||||
packages.add(new MonitoredPackage(packageName, duration,
|
packages.add(new MonitoredPackage(packageName, duration,
|
||||||
hasPassedHealthCheck));
|
healthCheckDuration, hasPassedHealthCheck));
|
||||||
}
|
}
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
|
Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
|
||||||
@@ -819,21 +876,50 @@ public class PackageWatchdog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Represents a package along with the time it should be monitored for. */
|
/**
|
||||||
static class MonitoredPackage {
|
* Represents a package along with the time it should be monitored for.
|
||||||
|
*
|
||||||
|
* <p> Note, the PackageWatchdog#mLock must always be held when reading or writing
|
||||||
|
* instances of this class.
|
||||||
|
*/
|
||||||
|
//TODO(b/120598832): Remove 'm' from non-private fields
|
||||||
|
private static class MonitoredPackage {
|
||||||
|
// Health check states
|
||||||
|
// mName has not passed health check but has requested a health check
|
||||||
|
public static int STATE_ACTIVE = 0;
|
||||||
|
// mName has not passed health check and has not requested a health check
|
||||||
|
public static int STATE_INACTIVE = 1;
|
||||||
|
// mName has passed health check
|
||||||
|
public static int STATE_PASSED = 2;
|
||||||
|
|
||||||
public final String mName;
|
public final String mName;
|
||||||
// Whether an explicit health check has passed
|
// Whether an explicit health check has passed
|
||||||
|
@GuardedBy("mLock")
|
||||||
public boolean mHasPassedHealthCheck;
|
public boolean mHasPassedHealthCheck;
|
||||||
// System uptime duration to monitor package
|
// System uptime duration to monitor package
|
||||||
|
@GuardedBy("mLock")
|
||||||
public long mDurationMs;
|
public long mDurationMs;
|
||||||
|
// System uptime duration to check the result of an explicit health check
|
||||||
|
// Initially, MAX_VALUE until we get a value from the health check service
|
||||||
|
// and request health checks.
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
public long mHealthCheckDurationMs = Long.MAX_VALUE;
|
||||||
// System uptime of first package failure
|
// System uptime of first package failure
|
||||||
|
@GuardedBy("mLock")
|
||||||
private long mUptimeStartMs;
|
private long mUptimeStartMs;
|
||||||
// Number of failures since mUptimeStartMs
|
// Number of failures since mUptimeStartMs
|
||||||
|
@GuardedBy("mLock")
|
||||||
private int mFailures;
|
private int mFailures;
|
||||||
|
|
||||||
MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
|
MonitoredPackage(String name, long durationMs, boolean hasPassedHealthCheck) {
|
||||||
|
this(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
MonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
|
||||||
|
boolean hasPassedHealthCheck) {
|
||||||
mName = name;
|
mName = name;
|
||||||
mDurationMs = durationMs;
|
mDurationMs = durationMs;
|
||||||
|
mHealthCheckDurationMs = healthCheckDurationMs;
|
||||||
mHasPassedHealthCheck = hasPassedHealthCheck;
|
mHasPassedHealthCheck = hasPassedHealthCheck;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,7 +928,8 @@ public class PackageWatchdog {
|
|||||||
*
|
*
|
||||||
* @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
|
* @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
public synchronized boolean onFailure() {
|
@GuardedBy("mLock")
|
||||||
|
public boolean onFailure() {
|
||||||
final long now = SystemClock.uptimeMillis();
|
final long now = SystemClock.uptimeMillis();
|
||||||
final long duration = now - mUptimeStartMs;
|
final long duration = now - mUptimeStartMs;
|
||||||
if (duration > TRIGGER_DURATION_MS) {
|
if (duration > TRIGGER_DURATION_MS) {
|
||||||
@@ -860,5 +947,20 @@ public class PackageWatchdog {
|
|||||||
}
|
}
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns any of the health check states of {@link #STATE_ACTIVE},
|
||||||
|
* {@link #STATE_INACTIVE} or {@link #STATE_PASSED}
|
||||||
|
*/
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
public int getHealthCheckState() {
|
||||||
|
if (mHasPassedHealthCheck) {
|
||||||
|
return STATE_PASSED;
|
||||||
|
} else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
|
||||||
|
return STATE_INACTIVE;
|
||||||
|
} else {
|
||||||
|
return STATE_ACTIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -661,8 +661,10 @@ public class PackageWatchdogTest {
|
|||||||
if (mIsEnabled) {
|
if (mIsEnabled) {
|
||||||
packages.retainAll(mSupportedPackages);
|
packages.retainAll(mSupportedPackages);
|
||||||
mRequestedPackages.addAll(packages);
|
mRequestedPackages.addAll(packages);
|
||||||
}
|
|
||||||
mSupportedConsumer.accept(mSupportedPackages);
|
mSupportedConsumer.accept(mSupportedPackages);
|
||||||
|
} else {
|
||||||
|
mSupportedConsumer.accept(Collections.emptyList());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSupportedPackages(List<String> packages) {
|
public void setSupportedPackages(List<String> packages) {
|
||||||
|
|||||||
Reference in New Issue
Block a user