Merge "Prevent PreRebootLogger blocking shutdown thread" into rvc-dev am: 018d5b2959

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11745991

Change-Id: Id622818f5a2f401c43839cf6cf686b898ca11b7e
This commit is contained in:
TreeHugger Robot
2020-06-09 10:40:04 +00:00
committed by Automerger Merge Worker
2 changed files with 84 additions and 24 deletions

View File

@@ -16,6 +16,7 @@
package com.android.server.power; package com.android.server.power;
import android.annotation.DurationMillisLong;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.content.Context; import android.content.Context;
import android.os.Environment; import android.os.Environment;
@@ -23,7 +24,7 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.provider.Settings; import android.provider.Settings.Global;
import android.util.Slog; import android.util.Slog;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
@@ -34,6 +35,8 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Provides utils to dump/wipe pre-reboot information. * Provides utils to dump/wipe pre-reboot information.
@@ -46,10 +49,12 @@ final class PreRebootLogger {
private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"}; private static final String[] SERVICES_TO_DUMP = {Context.ROLLBACK_SERVICE, "package"};
private static final Object sLock = new Object(); private static final Object sLock = new Object();
private static final long MAX_DUMP_TIME = TimeUnit.SECONDS.toMillis(20);
/** /**
* Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if * Process pre-reboot information. Dump pre-reboot information to {@link #PREREBOOT_DIR} if
* enabled {@link Settings.Global#ADB_ENABLED}; wipe dumped information otherwise. * enabled {@link Settings.Global#ADB_ENABLED} and having active staged session; wipe dumped
* information otherwise.
*/ */
static void log(Context context) { static void log(Context context) {
log(context, getDumpDir()); log(context, getDumpDir());
@@ -57,28 +62,49 @@ final class PreRebootLogger {
@VisibleForTesting @VisibleForTesting
static void log(Context context, @NonNull File dumpDir) { static void log(Context context, @NonNull File dumpDir) {
if (Settings.Global.getInt( if (needDump(context)) {
context.getContentResolver(), Settings.Global.ADB_ENABLED, 0) == 1) { dump(dumpDir, MAX_DUMP_TIME);
Slog.d(TAG, "Dumping pre-reboot information...");
dump(dumpDir);
} else { } else {
Slog.d(TAG, "Wiping pre-reboot information...");
wipe(dumpDir); wipe(dumpDir);
} }
} }
private static void dump(@NonNull File dumpDir) { private static boolean needDump(Context context) {
synchronized (sLock) { return Global.getInt(context.getContentResolver(), Global.ADB_ENABLED, 0) == 1
for (String buffer : BUFFERS_TO_DUMP) { && !context.getPackageManager().getPackageInstaller()
dumpLogsLocked(dumpDir, buffer); .getActiveStagedSessions().isEmpty();
} }
for (String service : SERVICES_TO_DUMP) {
dumpServiceLocked(dumpDir, service); @VisibleForTesting
static void dump(@NonNull File dumpDir, @DurationMillisLong long maxWaitTime) {
Slog.d(TAG, "Dumping pre-reboot information...");
final AtomicBoolean done = new AtomicBoolean(false);
final Thread t = new Thread(() -> {
synchronized (sLock) {
for (String buffer : BUFFERS_TO_DUMP) {
dumpLogsLocked(dumpDir, buffer);
}
for (String service : SERVICES_TO_DUMP) {
dumpServiceLocked(dumpDir, service);
}
} }
done.set(true);
});
t.start();
try {
t.join(maxWaitTime);
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to dump pre-reboot information due to interrupted", e);
}
if (!done.get()) {
Slog.w(TAG, "Failed to dump pre-reboot information due to timeout");
} }
} }
private static void wipe(@NonNull File dumpDir) { private static void wipe(@NonNull File dumpDir) {
Slog.d(TAG, "Wiping pre-reboot information...");
synchronized (sLock) { synchronized (sLock) {
for (File file : dumpDir.listFiles()) { for (File file : dumpDir.listFiles()) {
file.delete(); file.delete();
@@ -109,7 +135,7 @@ final class PreRebootLogger {
{"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()}; {"logcat", "-d", "-b", buffer, "-f", dumpFile.getAbsolutePath()};
Runtime.getRuntime().exec(cmdline).waitFor(); Runtime.getRuntime().exec(cmdline).waitFor();
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
Slog.d(TAG, "Dump system log buffer before reboot fail", e); Slog.e(TAG, "Failed to dump system log buffer before reboot", e);
} }
} }
@@ -127,7 +153,7 @@ final class PreRebootLogger {
| ParcelFileDescriptor.MODE_WRITE_ONLY); | ParcelFileDescriptor.MODE_WRITE_ONLY);
binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class)); binder.dump(fd.getFileDescriptor(), ArrayUtils.emptyArray(String.class));
} catch (FileNotFoundException | RemoteException e) { } catch (FileNotFoundException | RemoteException e) {
Slog.d(TAG, String.format("Dump %s service before reboot fail", serviceName), e); Slog.e(TAG, String.format("Failed to dump %s service before reboot", serviceName), e);
} }
} }
} }

View File

@@ -23,6 +23,9 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.provider.Settings; import android.provider.Settings;
import android.test.mock.MockContentResolver; import android.test.mock.MockContentResolver;
@@ -36,12 +39,15 @@ import com.google.common.io.Files;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.io.File; import java.io.File;
import java.util.List;
/** /**
* Tests for {@link PreRebootLogger} * Tests for {@link PreRebootLogger}
@@ -49,7 +55,11 @@ import java.io.File;
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class PreRebootLoggerTest { public class PreRebootLoggerTest {
@Rule public final MockitoRule mocks = MockitoJUnit.rule();
@Mock Context mContext; @Mock Context mContext;
@Mock PackageManager mPackageManager;
@Mock PackageInstaller mPackageInstaller;
@Mock List<SessionInfo> mSessions;
private MockContentResolver mContentResolver; private MockContentResolver mContentResolver;
private File mDumpDir; private File mDumpDir;
@@ -64,29 +74,53 @@ public class PreRebootLoggerTest {
} }
@Before @Before
public void setup() { public void enableAdbConfig() {
MockitoAnnotations.initMocks(this);
mContentResolver = new MockContentResolver(getInstrumentation().getTargetContext()); mContentResolver = new MockContentResolver(getInstrumentation().getTargetContext());
when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getContentResolver()).thenReturn(mContentResolver);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
}
@Before
public void prepareActiveStagedSessions() {
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getPackageInstaller()).thenReturn(mPackageInstaller);
when(mPackageInstaller.getActiveStagedSessions()).thenReturn(mSessions);
when(mSessions.isEmpty()).thenReturn(false);
}
@Before
public void setupDumpDir() {
mDumpDir = Files.createTempDir(); mDumpDir = Files.createTempDir();
mDumpDir.mkdir();
mDumpDir.deleteOnExit(); mDumpDir.deleteOnExit();
} }
@Test @Test
public void log_adbEnabled_dumpsInformationProperly() { public void log_dumpsInformationProperly() {
Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
PreRebootLogger.log(mContext, mDumpDir); PreRebootLogger.log(mContext, mDumpDir);
assertThat(mDumpDir.list()).asList().containsExactly("system", "package", "rollback"); assertThat(mDumpDir.list()).asList().containsExactly("system", "package", "rollback");
} }
@Test
public void dump_exceedTimeout_wontBlockCurrentThread() {
PreRebootLogger.dump(mDumpDir, 1 /* maxWaitTime */);
assertThat(mDumpDir.listFiles()).asList().containsNoneOf("system", "package", "rollback");
}
@Test
public void log_noActiveStagedSession_wipesDumpedInformation() {
PreRebootLogger.log(mContext, mDumpDir);
when(mSessions.isEmpty()).thenReturn(true);
PreRebootLogger.log(mContext, mDumpDir);
assertThat(mDumpDir.listFiles()).isEmpty();
}
@Test @Test
public void log_adbDisabled_wipesDumpedInformation() { public void log_adbDisabled_wipesDumpedInformation() {
Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 1);
PreRebootLogger.log(mContext, mDumpDir); PreRebootLogger.log(mContext, mDumpDir);
Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 0); Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, 0);