Delete runtime images when deleting artifacts and after dexopt.
The deletion is performed on `dexoptPackage` (`pm compile`),
`resetDexoptStatus` (`pm compile --reset`), and `deleteDexoptArtifacts`
(`pm delete-dexopt`).
Bug: 299442352
Test: atest ArtServiceTests
Test: adb shell pm compile -m verify -f com.android.settings
Test: adb shell pm compile --reset com.android.settings
Test: adb shell pm delete-dexopt com.android.settings
Change-Id: I165ef7c500d8dddbc1cf05c8d6b29400ff593d3a
Merged-In: I165ef7c500d8dddbc1cf05c8d6b29400ff593d3a
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
index 4445ecd..4a1b75e 100644
--- a/libartservice/service/java/com/android/server/art/AidlUtils.java
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -179,6 +179,16 @@
}
@NonNull
+ public static RuntimeArtifactsPath buildRuntimeArtifactsPath(
+ @NonNull String packageName, @NonNull String dexPath, @NonNull String isa) {
+ var runtimeArtifactsPath = new RuntimeArtifactsPath();
+ runtimeArtifactsPath.packageName = packageName;
+ runtimeArtifactsPath.dexPath = dexPath;
+ runtimeArtifactsPath.isa = isa;
+ return runtimeArtifactsPath;
+ }
+
+ @NonNull
public static String toString(@NonNull PrimaryRefProfilePath profile) {
return String.format("PrimaryRefProfilePath[packageName = %s, profileName = %s]",
profile.packageName, profile.profileName);
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 4a3c28f..1b6c98e 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -179,6 +179,8 @@
* Deletes dexopt artifacts of a package, including the artifacts for primary dex files and the
* ones for secondary dex files. This includes VDEX, ODEX, and ART files.
*
+ * Also deletes runtime artifacts of the package, though they are not dexopt artifacts.
+ *
* @throws IllegalArgumentException if the package is not found or the flags are illegal
* @throws IllegalStateException if the operation encounters an error that should never happen
* (e.g., an internal logic error).
@@ -201,6 +203,9 @@
for (Abi abi : Utils.getAllAbis(pkgState)) {
freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath(
dexInfo.dexPath(), abi.isa(), isInDalvikCache));
+ freedBytes += mInjector.getArtd().deleteRuntimeArtifacts(
+ AidlUtils.buildRuntimeArtifactsPath(
+ packageName, dexInfo.dexPath(), abi.isa()));
}
}
@@ -357,6 +362,10 @@
* Dexopts a package. The time this operation takes ranges from a few milliseconds to several
* minutes, depending on the params and the code size of the package.
*
+ * When dexopt is successfully performed for a dex container file, this operation also deletes
+ * the corresponding runtime artifacts (the ART files in the package's data directory, which are
+ * generated by the runtime, not by dexopt).
+ *
* When this operation ends (either completed or cancelled), callbacks added by {@link
* #addDexoptDoneCallback(Executor, DexoptDoneCallback)} are called.
*
@@ -389,9 +398,9 @@
/**
* Resets the dexopt state of the package as if the package is newly installed.
*
- * More specifically, it clears reference profiles, current profiles, and any code compiled from
- * those local profiles. If there is an external profile (e.g., a cloud profile), the code
- * compiled from that profile will be kept.
+ * More specifically, it clears reference profiles, current profiles, any code compiled from
+ * those local profiles, and runtime artifacts. If there is an external profile (e.g., a cloud
+ * profile), the code compiled from that profile will be kept.
*
* For secondary dex files, it also clears all dexopt artifacts.
*
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
index e822d31..dbfe43d 100644
--- a/libartservice/service/java/com/android/server/art/Dexopter.java
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -476,9 +476,25 @@
dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
}
- return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
- target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
- dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
+ ArtdDexoptResult result = mInjector.getArtd().dexopt(outputArtifacts,
+ target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
+ target.compilerFilter(), profile, inputVdex, dmFile, priorityClass, dexoptOptions,
+ artdCancellationSignal);
+
+ // Delete the existing runtime images after the dexopt is performed, even if they are still
+ // usable (e.g., the compiler filter is "verify"). This is to make sure the dexopt puts the
+ // dex file into a certain dexopt state, to make it easier for debugging and testing. It's
+ // also an optimization to release disk space as soon as possible. However, not doing the
+ // deletion here does not affect correctness or waste disk space: if the existing runtime
+ // images are still usable, technically, they can still be used to improve runtime
+ // performance; if they are no longer usable, they will be deleted by the file GC during the
+ // daily background dexopt job anyway.
+ if (!result.cancelled) {
+ mInjector.getArtd().deleteRuntimeArtifacts(AidlUtils.buildRuntimeArtifactsPath(
+ mPkgState.getPackageName(), target.dexInfo().dexPath(), target.isa()));
+ }
+
+ return result;
}
@Nullable
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index 983bc9c..7e41f32 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -237,10 +237,15 @@
@Test
public void testdeleteDexoptArtifacts() throws Exception {
- when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+ final long DEXOPT_ARTIFACTS_FREED = 1l;
+ final long RUNTIME_ARTIFACTS_FREED = 100l;
+
+ when(mArtd.deleteArtifacts(any())).thenReturn(DEXOPT_ARTIFACTS_FREED);
+ when(mArtd.deleteRuntimeArtifacts(any())).thenReturn(RUNTIME_ARTIFACTS_FREED);
DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
- assertThat(result.getFreedBytes()).isEqualTo(5);
+ assertThat(result.getFreedBytes())
+ .isEqualTo(5 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED);
verify(mArtd).deleteArtifacts(deepEq(
AidlUtils.buildArtifactsPath("/data/app/foo/base.apk", "arm64", mIsInDalvikCache)));
@@ -253,12 +258,25 @@
verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
"/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/base.apk", "arm64")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(
+ AidlUtils.buildRuntimeArtifactsPath(PKG_NAME_1, "/data/app/foo/base.apk", "arm")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/split_0.apk", "arm64")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/split_0.apk", "arm")));
+
// Verify that there are no more calls than the ones above.
verify(mArtd, times(5)).deleteArtifacts(any());
+ verify(mArtd, times(4)).deleteRuntimeArtifacts(any());
}
@Test
public void testdeleteDexoptArtifactsTranslatedIsas() throws Exception {
+ final long DEXOPT_ARTIFACTS_FREED = 1l;
+ final long RUNTIME_ARTIFACTS_FREED = 100l;
+
lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm64")).thenReturn("x86_64");
lenient().when(SystemProperties.get("ro.dalvik.vm.isa.arm")).thenReturn("x86");
lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64");
@@ -266,10 +284,12 @@
lenient().when(Constants.getNative32BitAbi()).thenReturn("x86");
when(mSecondaryDexInfo1.get(0).abiNames()).thenReturn(Set.of("x86_64"));
- when(mArtd.deleteArtifacts(any())).thenReturn(1l);
+ when(mArtd.deleteArtifacts(any())).thenReturn(DEXOPT_ARTIFACTS_FREED);
+ when(mArtd.deleteRuntimeArtifacts(any())).thenReturn(RUNTIME_ARTIFACTS_FREED);
DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1);
- assertThat(result.getFreedBytes()).isEqualTo(5);
+ assertThat(result.getFreedBytes())
+ .isEqualTo(5 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED);
verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
"/data/app/foo/base.apk", "x86_64", mIsInDalvikCache)));
@@ -279,12 +299,23 @@
"/data/app/foo/split_0.apk", "x86_64", mIsInDalvikCache)));
verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
"/data/app/foo/split_0.apk", "x86", mIsInDalvikCache)));
+
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/base.apk", "x86_64")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(
+ AidlUtils.buildRuntimeArtifactsPath(PKG_NAME_1, "/data/app/foo/base.apk", "x86")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/split_0.apk", "x86_64")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/split_0.apk", "x86")));
+
// We assume that the ISA got from `DexUseManagerLocal` is already the translated one.
verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
"/data/user/0/foo/1.apk", "x86_64", false /* isInDalvikCache */)));
// Verify that there are no more calls than the ones above.
verify(mArtd, times(5)).deleteArtifacts(any());
+ verify(mArtd, times(4)).deleteRuntimeArtifacts(any());
}
@Test(expected = IllegalArgumentException.class)
@@ -450,6 +481,15 @@
verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath(
"/data/app/foo/split_0.apk", "arm", mIsInDalvikCache)));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/base.apk", "arm64")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(
+ AidlUtils.buildRuntimeArtifactsPath(PKG_NAME_1, "/data/app/foo/base.apk", "arm")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/split_0.apk", "arm64")));
+ verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
+ PKG_NAME_1, "/data/app/foo/split_0.apk", "arm")));
+
verify(mArtd).deleteProfile(
deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk")));
verify(mArtd).deleteProfile(
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
index a9f0cd1..04f70dd 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -272,6 +272,13 @@
isNull() /* inputVdex */, isNull() /* dmFile */,
eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
+ // Only delete runtime artifacts for successful dexopt operations, namely the first one and
+ // the fourth one.
+ doReturn(1l).when(mArtd).deleteRuntimeArtifacts(deepEq(
+ AidlUtils.buildRuntimeArtifactsPath(PKG_NAME, "/data/app/foo/base.apk", "arm64")));
+ doReturn(1l).when(mArtd).deleteRuntimeArtifacts(deepEq(
+ AidlUtils.buildRuntimeArtifactsPath(PKG_NAME, "/data/app/foo/split_0.apk", "arm")));
+
assertThat(mPrimaryDexopter.dexopt())
.comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
.containsExactly(