Loading startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +89 −60 Original line number Diff line number Diff line Loading @@ -42,15 +42,20 @@ import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.PackageManagerService; import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; import com.android.server.wm.ActivityMetricsLaunchObserverRegistry; import com.android.server.wm.ActivityTaskManagerInternal; import java.time.Duration; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; import java.util.HashMap; import java.util.List; /** * System-server-local proxy into the {@code IIorap} native service. Loading @@ -65,6 +70,7 @@ public class IorapForwardingService extends SystemService { /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ private static boolean WTF_CRASH = SystemProperties.getBoolean( "iorapd.forwarding_service.wtf_crash", false); private static final Duration TIMEOUT = Duration.ofSeconds(600L); // "Unique" job ID from the service name. Also equal to 283673059. public static final int JOB_ID_IORAPD = encodeEnglishAlphabetStringIntoInt("iorapd"); Loading @@ -80,6 +86,12 @@ public class IorapForwardingService extends SystemService { private volatile IorapdJobService mJobService; // Write-once (null -> non-null forever). private volatile static IorapForwardingService sSelfService; // Write once (null -> non-null). /** * Atomics set to true if the JobScheduler requests an abort. */ private final AtomicBoolean mAbortIdleCompilation = new AtomicBoolean(false); /** * Initializes the system service. * <p> Loading Loading @@ -542,65 +554,94 @@ public class IorapForwardingService extends SystemService { // Tell iorapd to start a background job. Log.d(TAG, "Starting background job: " + params.toString()); mAbortIdleCompilation.set(false); // PackageManagerService starts before IORap service. PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); List<String> pkgs = pm.getAllPackages(); runIdleCompilationAsync(params, pkgs); return true; } private void runIdleCompilationAsync(final JobParameters params, final List<String> pkgs) { new Thread("IORap_IdleCompilation") { @Override public void run() { for (int i = 0; i < pkgs.size(); i++) { String pkg = pkgs.get(i); if (mAbortIdleCompilation.get()) { Log.i(TAG, "The idle compilation is aborted"); return; } // We wait until that job's sequence ID returns to us with 'Completed', RequestId request; synchronized (mLock) { // TODO: would be cleaner if we got the request from the 'invokeRemote' function. // Better yet, consider a Pair<RequestId, Future<TaskResult>> or similar. // TODO: would be cleaner if we got the request from the // 'invokeRemote' function. Better yet, consider // a Pair<RequestId, Future<TaskResult>> or similar. request = RequestId.nextValueForSequence(); mRunningJobs.put(request, params); } Log.i(TAG, String.format("IORap compile package: %s, [%d/%d]", pkg, i + 1, pkgs.size())); boolean shouldUpdateVersions = (i == 0); if (!invokeRemote(mIorapRemote, (IIorap remote) -> remote.onJobScheduledEvent(request, JobScheduledEvent.createIdleMaintenance( JobScheduledEvent.TYPE_START_JOB, params)) )) { params, pkg, shouldUpdateVersions)))) { synchronized (mLock) { mRunningJobs.remove(request); // Avoid memory leaks. } } // Something went wrong on the remote side. Treat the job as being // 'already finished' (i.e. immediately release wake lock). return false; // Wait until the job is complete and removed from the running jobs. retryWithTimeout(TIMEOUT, () -> { synchronized (mLock) { return !mRunningJobs.containsKey(request); } }); } // Finish the job after all packages are compiled. if (mProxy != null) { mProxy.jobFinished(params, /*reschedule*/false); } } }.start(); } // True -> keep the wakelock acquired until #jobFinished is called. /** Retry until timeout. */ private boolean retryWithTimeout(final Duration timeout, BooleanSupplier supplier) { long totalSleepTimeMs = 0L; long sleepIntervalMs = 10L; while (true) { if (supplier.getAsBoolean()) { return true; } try { TimeUnit.MILLISECONDS.sleep(sleepIntervalMs); } catch (InterruptedException e) { Log.e(TAG, e.getMessage()); return false; } totalSleepTimeMs += sleepIntervalMs; if (totalSleepTimeMs > timeout.toMillis()) { return false; } } } // Called by system to prematurely stop the job. @Override public boolean onStopJob(JobParameters params) { // As this is unexpected behavior, print a warning. Log.w(TAG, "onStopJob(params=" + params.toString() + ")"); // No longer track this job (avoids a memory leak). boolean wasTracking = false; synchronized (mLock) { for (HashMap.Entry<RequestId, JobParameters> entry : mRunningJobs.entrySet()) { if (entry.getValue().getJobId() == params.getJobId()) { mRunningJobs.remove(entry.getKey()); wasTracking = true; } } } // Notify iorapd to stop (abort) the job. if (wasTracking) { invokeRemote(mIorapRemote, (IIorap remote) -> remote.onJobScheduledEvent(RequestId.nextValueForSequence(), JobScheduledEvent.createIdleMaintenance( JobScheduledEvent.TYPE_STOP_JOB, params)) ); } else { // Even weirder. This could only be considered "correct" if iorapd reported success // concurrently to the JobService requesting an onStopJob. Log.e(TAG, "Untracked onStopJob request"); // see above Log.w for the params. } mAbortIdleCompilation.set(true); // Yes, retry the job at a later time no matter what. return true; Loading @@ -626,18 +667,6 @@ public class IorapForwardingService extends SystemService { } Log.d(TAG, "Finished background job: " + jobParameters.toString()); // Job is successful and periodic. Do not 'reschedule' according to the back-off // criteria. // // This releases the wakelock that was acquired in #onStartJob. IorapdJobServiceProxy proxy = mProxy; if (proxy != null) { proxy.jobFinished(jobParameters, /*reschedule*/false); } // Cannot call 'jobFinished' on 'this' because it was not constructed // from the JobService, so it would get an NPE when calling mEngine. } public void onIorapdDisconnected() { Loading startop/iorap/src/com/google/android/startop/iorap/JobScheduledEvent.java +25 −5 Original line number Diff line number Diff line Loading @@ -55,6 +55,10 @@ public class JobScheduledEvent implements Parcelable { /** @see JobParameters#getJobId() */ public final int jobId; public final String packageName; public final boolean shouldUpdateVersions; /** Device is 'idle' and it's charging (plugged in). */ public static final int SORT_IDLE_MAINTENANCE = 0; private static final int SORT_MAX = 0; Loading @@ -77,14 +81,22 @@ public class JobScheduledEvent implements Parcelable { * Only the job ID is retained from {@code jobParams}, all other param info is dropped. */ @NonNull public static JobScheduledEvent createIdleMaintenance(@Type int type, JobParameters jobParams) { return new JobScheduledEvent(type, jobParams.getJobId(), SORT_IDLE_MAINTENANCE); public static JobScheduledEvent createIdleMaintenance( @Type int type, JobParameters jobParams, String packageName, boolean shouldUpdateVersions) { return new JobScheduledEvent( type, jobParams.getJobId(), SORT_IDLE_MAINTENANCE, packageName, shouldUpdateVersions); } private JobScheduledEvent(@Type int type, int jobId, @Sort int sort) { private JobScheduledEvent(@Type int type, int jobId, @Sort int sort, String packageName, boolean shouldUpdateVersions) { this.type = type; this.jobId = jobId; this.sort = sort; this.packageName = packageName; this.shouldUpdateVersions = shouldUpdateVersions; checkConstructorArguments(); } Loading @@ -108,12 +120,16 @@ public class JobScheduledEvent implements Parcelable { private boolean equals(JobScheduledEvent other) { return type == other.type && jobId == other.jobId && sort == other.sort; sort == other.sort && packageName.equals(other.packageName) && shouldUpdateVersions == other.shouldUpdateVersions; } @Override public String toString() { return String.format("{type: %d, jobId: %d, sort: %d}", type, jobId, sort); return String.format( "{type: %d, jobId: %d, sort: %d, packageName: %s, shouldUpdateVersions %b}", type, jobId, sort, packageName, shouldUpdateVersions); } //<editor-fold desc="Binder boilerplate"> Loading @@ -122,6 +138,8 @@ public class JobScheduledEvent implements Parcelable { out.writeInt(type); out.writeInt(jobId); out.writeInt(sort); out.writeString(packageName); out.writeBoolean(shouldUpdateVersions); // We do not parcel the entire JobParameters here because there is no C++ equivalent // of that class [which the iorapd side of the binder interface requires]. Loading @@ -131,6 +149,8 @@ public class JobScheduledEvent implements Parcelable { this.type = in.readInt(); this.jobId = in.readInt(); this.sort = in.readInt(); this.packageName = in.readString(); this.shouldUpdateVersions = in.readBoolean(); checkConstructorArguments(); } Loading Loading
startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java +89 −60 Original line number Diff line number Diff line Loading @@ -42,15 +42,20 @@ import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.PackageManagerService; import com.android.server.wm.ActivityMetricsLaunchObserver; import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; import com.android.server.wm.ActivityMetricsLaunchObserverRegistry; import com.android.server.wm.ActivityTaskManagerInternal; import java.time.Duration; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; import java.util.HashMap; import java.util.List; /** * System-server-local proxy into the {@code IIorap} native service. Loading @@ -65,6 +70,7 @@ public class IorapForwardingService extends SystemService { /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ private static boolean WTF_CRASH = SystemProperties.getBoolean( "iorapd.forwarding_service.wtf_crash", false); private static final Duration TIMEOUT = Duration.ofSeconds(600L); // "Unique" job ID from the service name. Also equal to 283673059. public static final int JOB_ID_IORAPD = encodeEnglishAlphabetStringIntoInt("iorapd"); Loading @@ -80,6 +86,12 @@ public class IorapForwardingService extends SystemService { private volatile IorapdJobService mJobService; // Write-once (null -> non-null forever). private volatile static IorapForwardingService sSelfService; // Write once (null -> non-null). /** * Atomics set to true if the JobScheduler requests an abort. */ private final AtomicBoolean mAbortIdleCompilation = new AtomicBoolean(false); /** * Initializes the system service. * <p> Loading Loading @@ -542,65 +554,94 @@ public class IorapForwardingService extends SystemService { // Tell iorapd to start a background job. Log.d(TAG, "Starting background job: " + params.toString()); mAbortIdleCompilation.set(false); // PackageManagerService starts before IORap service. PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); List<String> pkgs = pm.getAllPackages(); runIdleCompilationAsync(params, pkgs); return true; } private void runIdleCompilationAsync(final JobParameters params, final List<String> pkgs) { new Thread("IORap_IdleCompilation") { @Override public void run() { for (int i = 0; i < pkgs.size(); i++) { String pkg = pkgs.get(i); if (mAbortIdleCompilation.get()) { Log.i(TAG, "The idle compilation is aborted"); return; } // We wait until that job's sequence ID returns to us with 'Completed', RequestId request; synchronized (mLock) { // TODO: would be cleaner if we got the request from the 'invokeRemote' function. // Better yet, consider a Pair<RequestId, Future<TaskResult>> or similar. // TODO: would be cleaner if we got the request from the // 'invokeRemote' function. Better yet, consider // a Pair<RequestId, Future<TaskResult>> or similar. request = RequestId.nextValueForSequence(); mRunningJobs.put(request, params); } Log.i(TAG, String.format("IORap compile package: %s, [%d/%d]", pkg, i + 1, pkgs.size())); boolean shouldUpdateVersions = (i == 0); if (!invokeRemote(mIorapRemote, (IIorap remote) -> remote.onJobScheduledEvent(request, JobScheduledEvent.createIdleMaintenance( JobScheduledEvent.TYPE_START_JOB, params)) )) { params, pkg, shouldUpdateVersions)))) { synchronized (mLock) { mRunningJobs.remove(request); // Avoid memory leaks. } } // Something went wrong on the remote side. Treat the job as being // 'already finished' (i.e. immediately release wake lock). return false; // Wait until the job is complete and removed from the running jobs. retryWithTimeout(TIMEOUT, () -> { synchronized (mLock) { return !mRunningJobs.containsKey(request); } }); } // Finish the job after all packages are compiled. if (mProxy != null) { mProxy.jobFinished(params, /*reschedule*/false); } } }.start(); } // True -> keep the wakelock acquired until #jobFinished is called. /** Retry until timeout. */ private boolean retryWithTimeout(final Duration timeout, BooleanSupplier supplier) { long totalSleepTimeMs = 0L; long sleepIntervalMs = 10L; while (true) { if (supplier.getAsBoolean()) { return true; } try { TimeUnit.MILLISECONDS.sleep(sleepIntervalMs); } catch (InterruptedException e) { Log.e(TAG, e.getMessage()); return false; } totalSleepTimeMs += sleepIntervalMs; if (totalSleepTimeMs > timeout.toMillis()) { return false; } } } // Called by system to prematurely stop the job. @Override public boolean onStopJob(JobParameters params) { // As this is unexpected behavior, print a warning. Log.w(TAG, "onStopJob(params=" + params.toString() + ")"); // No longer track this job (avoids a memory leak). boolean wasTracking = false; synchronized (mLock) { for (HashMap.Entry<RequestId, JobParameters> entry : mRunningJobs.entrySet()) { if (entry.getValue().getJobId() == params.getJobId()) { mRunningJobs.remove(entry.getKey()); wasTracking = true; } } } // Notify iorapd to stop (abort) the job. if (wasTracking) { invokeRemote(mIorapRemote, (IIorap remote) -> remote.onJobScheduledEvent(RequestId.nextValueForSequence(), JobScheduledEvent.createIdleMaintenance( JobScheduledEvent.TYPE_STOP_JOB, params)) ); } else { // Even weirder. This could only be considered "correct" if iorapd reported success // concurrently to the JobService requesting an onStopJob. Log.e(TAG, "Untracked onStopJob request"); // see above Log.w for the params. } mAbortIdleCompilation.set(true); // Yes, retry the job at a later time no matter what. return true; Loading @@ -626,18 +667,6 @@ public class IorapForwardingService extends SystemService { } Log.d(TAG, "Finished background job: " + jobParameters.toString()); // Job is successful and periodic. Do not 'reschedule' according to the back-off // criteria. // // This releases the wakelock that was acquired in #onStartJob. IorapdJobServiceProxy proxy = mProxy; if (proxy != null) { proxy.jobFinished(jobParameters, /*reschedule*/false); } // Cannot call 'jobFinished' on 'this' because it was not constructed // from the JobService, so it would get an NPE when calling mEngine. } public void onIorapdDisconnected() { Loading
startop/iorap/src/com/google/android/startop/iorap/JobScheduledEvent.java +25 −5 Original line number Diff line number Diff line Loading @@ -55,6 +55,10 @@ public class JobScheduledEvent implements Parcelable { /** @see JobParameters#getJobId() */ public final int jobId; public final String packageName; public final boolean shouldUpdateVersions; /** Device is 'idle' and it's charging (plugged in). */ public static final int SORT_IDLE_MAINTENANCE = 0; private static final int SORT_MAX = 0; Loading @@ -77,14 +81,22 @@ public class JobScheduledEvent implements Parcelable { * Only the job ID is retained from {@code jobParams}, all other param info is dropped. */ @NonNull public static JobScheduledEvent createIdleMaintenance(@Type int type, JobParameters jobParams) { return new JobScheduledEvent(type, jobParams.getJobId(), SORT_IDLE_MAINTENANCE); public static JobScheduledEvent createIdleMaintenance( @Type int type, JobParameters jobParams, String packageName, boolean shouldUpdateVersions) { return new JobScheduledEvent( type, jobParams.getJobId(), SORT_IDLE_MAINTENANCE, packageName, shouldUpdateVersions); } private JobScheduledEvent(@Type int type, int jobId, @Sort int sort) { private JobScheduledEvent(@Type int type, int jobId, @Sort int sort, String packageName, boolean shouldUpdateVersions) { this.type = type; this.jobId = jobId; this.sort = sort; this.packageName = packageName; this.shouldUpdateVersions = shouldUpdateVersions; checkConstructorArguments(); } Loading @@ -108,12 +120,16 @@ public class JobScheduledEvent implements Parcelable { private boolean equals(JobScheduledEvent other) { return type == other.type && jobId == other.jobId && sort == other.sort; sort == other.sort && packageName.equals(other.packageName) && shouldUpdateVersions == other.shouldUpdateVersions; } @Override public String toString() { return String.format("{type: %d, jobId: %d, sort: %d}", type, jobId, sort); return String.format( "{type: %d, jobId: %d, sort: %d, packageName: %s, shouldUpdateVersions %b}", type, jobId, sort, packageName, shouldUpdateVersions); } //<editor-fold desc="Binder boilerplate"> Loading @@ -122,6 +138,8 @@ public class JobScheduledEvent implements Parcelable { out.writeInt(type); out.writeInt(jobId); out.writeInt(sort); out.writeString(packageName); out.writeBoolean(shouldUpdateVersions); // We do not parcel the entire JobParameters here because there is no C++ equivalent // of that class [which the iorapd side of the binder interface requires]. Loading @@ -131,6 +149,8 @@ public class JobScheduledEvent implements Parcelable { this.type = in.readInt(); this.jobId = in.readInt(); this.sort = in.readInt(); this.packageName = in.readString(); this.shouldUpdateVersions = in.readBoolean(); checkConstructorArguments(); } Loading