Merge "Make settings cahches generation mechanism robust." into nyc-dev

am: b35301e421

* commit 'b35301e421bb08c28425c49ad46a277c96ccb411':
  Make settings cahches generation mechanism robust.

Change-Id: I59566be20a76084f409f0dd57c70337b60339c87
This commit is contained in:
Svetoslav Ganov
2016-05-13 22:26:29 +00:00
committed by android-build-merger
10 changed files with 502 additions and 117 deletions

View File

@@ -1462,12 +1462,15 @@ public final class Settings {
private static final class GenerationTracker {
private final MemoryIntArray mArray;
private final Runnable mErrorHandler;
private final int mIndex;
private int mCurrentGeneration;
public GenerationTracker(@NonNull MemoryIntArray array, int index) {
public GenerationTracker(@NonNull MemoryIntArray array, int index,
Runnable errorHandler) {
mArray = array;
mIndex = index;
mErrorHandler = errorHandler;
mCurrentGeneration = readCurrentGeneration();
}
@@ -1487,9 +1490,23 @@ public final class Settings {
return mArray.get(mIndex);
} catch (IOException e) {
Log.e(TAG, "Error getting current generation", e);
if (mErrorHandler != null) {
mErrorHandler.run();
}
}
return -1;
}
public void destroy() {
try {
mArray.close();
} catch (IOException e) {
Log.e(TAG, "Error closing backing array", e);
if (mErrorHandler != null) {
mErrorHandler.run();
}
}
}
}
// Thread-safe.
@@ -1616,7 +1633,20 @@ public final class Settings {
+ cr.getPackageName() + " and user:"
+ userHandle + " with index:" + index);
}
mGenerationTracker = new GenerationTracker(array, index);
mGenerationTracker = new GenerationTracker(array, index,
() -> {
synchronized (this) {
Log.e(TAG, "Error accessing generation"
+ " tracker - removing");
if (mGenerationTracker != null) {
GenerationTracker generationTracker =
mGenerationTracker;
mGenerationTracker = null;
generationTracker.destroy();
mValues.clear();
}
}
});
}
}
mValues.put(name, value);

View File

@@ -20,6 +20,7 @@ import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.Process;
import libcore.io.IoUtils;
import java.io.Closeable;
import java.io.IOException;
@@ -46,6 +47,8 @@ import java.util.UUID;
* @hide
*/
public final class MemoryIntArray implements Parcelable, Closeable {
private static final String TAG = "MemoryIntArray";
private static final int MAX_SIZE = 1024;
private final int mOwnerPid;
@@ -142,8 +145,9 @@ public final class MemoryIntArray implements Parcelable, Closeable {
@Override
public void close() throws IOException {
if (!isClosed()) {
nativeClose(mFd.getFd(), mMemoryAddr, isOwner());
ParcelFileDescriptor pfd = mFd;
mFd = null;
nativeClose(pfd.getFd(), mMemoryAddr, isOwner());
}
}
@@ -156,7 +160,7 @@ public final class MemoryIntArray implements Parcelable, Closeable {
@Override
protected void finalize() throws Throwable {
close();
IoUtils.closeQuietly(this);
super.finalize();
}
@@ -230,7 +234,6 @@ public final class MemoryIntArray implements Parcelable, Closeable {
private native int nativeGet(int fd, long memoryAddr, int index, boolean owner);
private native void nativeSet(int fd, long memoryAddr, int index, int value, boolean owner);
private native int nativeSize(int fd);
private native static int nativeGetMemoryPageSize();
/**
* @return The max array size.
@@ -246,7 +249,8 @@ public final class MemoryIntArray implements Parcelable, Closeable {
try {
return new MemoryIntArray(parcel);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
Log.e(TAG, "Error unparceling MemoryIntArray");
return null;
}
}

View File

@@ -14,9 +14,9 @@
* limitations under the License.
*/
#include "core_jni_helpers.h"
#include <cutils/ashmem.h>
#include <linux/ashmem.h>
#include <sys/mman.h>
namespace android {
@@ -44,11 +44,6 @@ static jint android_util_MemoryIntArray_create(JNIEnv* env, jobject clazz, jstri
return -1;
}
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
jniThrowException(env, "java/io/IOException", "ashmem was purged");
return -1;
}
int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
if (setProtResult < 0) {
jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
@@ -133,24 +128,13 @@ static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
return -1;
}
bool unpin = false;
if (!owner) {
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
return -1;
}
unpin = true;
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
return -1;
}
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
const int result = value->load(std::memory_order_relaxed);
if (unpin) {
ashmem_unpin_region(fd, 0, 0);
}
return result;
return value->load(std::memory_order_relaxed);
}
static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
@@ -161,22 +145,13 @@ static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
return;
}
bool unpin = false;
if (!owner) {
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
return;
}
unpin = true;
if (ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
jniThrowException(env, "java/io/IOException", "ashmem region was purged");
return;
}
std::atomic_int* value = reinterpret_cast<std::atomic_int*>(address) + index;
value->store(newValue, std::memory_order_relaxed);
if (unpin) {
ashmem_unpin_region(fd, 0, 0);
}
}
static jint android_util_MemoryIntArray_size(JNIEnv* env, jobject clazz, jint fd) {

View File

@@ -10,6 +10,7 @@ LOCAL_MODULE_TAGS := tests
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += src/android/util/IRemoteMemoryIntArray.aidl
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \

View File

@@ -43,6 +43,11 @@
<application>
<uses-library android:name="android.test.runner" />
<service android:name="android.util.RemoteMemoryIntArrayService"
android:process=":remote">
</service>
</application>
<instrumentation

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util;
import android.util.MemoryIntArray;
interface IRemoteMemoryIntArray {
MemoryIntArray peekInstance();
void create(int size, boolean clientWritable);
boolean isWritable();
int get(int index);
void set(int index, int value);
int size();
void close();
boolean isClosed();
}

View File

@@ -16,12 +16,22 @@
package android.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.os.Parcel;
import junit.framework.TestCase;
import android.support.test.runner.AndroidJUnit4;
import libcore.io.IoUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
public class MemoryIntArrayTest extends TestCase {
@RunWith(AndroidJUnit4.class)
public class MemoryIntArrayTest {
@Test
public void testSize() throws Exception {
MemoryIntArray array = null;
try {
@@ -32,6 +42,7 @@ public class MemoryIntArrayTest extends TestCase {
}
}
@Test
public void testGetSet() throws Exception {
MemoryIntArray array = null;
try {
@@ -49,6 +60,7 @@ public class MemoryIntArrayTest extends TestCase {
}
}
@Test
public void testWritable() throws Exception {
MemoryIntArray array = null;
try {
@@ -59,6 +71,7 @@ public class MemoryIntArrayTest extends TestCase {
}
}
@Test
public void testClose() throws Exception {
MemoryIntArray array = null;
try {
@@ -72,6 +85,7 @@ public class MemoryIntArrayTest extends TestCase {
}
}
@Test
public void testMarshalledGetSet() throws Exception {
MemoryIntArray firstArray = null;
MemoryIntArray secondArray = null;
@@ -99,6 +113,7 @@ public class MemoryIntArrayTest extends TestCase {
}
}
@Test
public void testInteractOnceClosed() throws Exception {
MemoryIntArray array = null;
try {
@@ -141,6 +156,7 @@ public class MemoryIntArrayTest extends TestCase {
}
}
@Test
public void testInteractPutOfBounds() throws Exception {
MemoryIntArray array = null;
try {
@@ -178,6 +194,7 @@ public class MemoryIntArrayTest extends TestCase {
}
}
@Test
public void testOverMaxSize() throws Exception {
MemoryIntArray array = null;
try {
@@ -189,4 +206,28 @@ public class MemoryIntArrayTest extends TestCase {
IoUtils.closeQuietly(array);
}
}
@Test
public void testNotMutableByUnprivilegedClients() throws Exception {
RemoteIntArray remoteIntArray = new RemoteIntArray(1, false);
try {
assertNotNull("Couldn't get remote instance", remoteIntArray);
MemoryIntArray localIntArray = remoteIntArray.peekInstance();
assertNotNull("Couldn't get local instance", localIntArray);
remoteIntArray.set(0, 1);
assertSame("Remote should be able to modify", 1, remoteIntArray.get(0));
try {
localIntArray.set(0, 0);
fail("Local shouldn't be able to modify");
} catch (UnsupportedOperationException e) {
/* expected */
}
assertSame("Local shouldn't be able to modify", 1, localIntArray.get(0));
assertSame("Local shouldn't be able to modify", 1, remoteIntArray.get(0));
} finally {
remoteIntArray.destroy();
}
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
final class RemoteIntArray implements ServiceConnection, Closeable {
private static final long BIND_REMOTE_SERVICE_TIMEOUT =
("eng".equals(Build.TYPE)) ? 120000 : 10000;
private final Object mLock = new Object();
private final Intent mIntent = new Intent();
private android.util.IRemoteMemoryIntArray mRemoteInstance;
public RemoteIntArray(int size, boolean clientWritable) throws IOException, TimeoutException {
mIntent.setComponent(new ComponentName(InstrumentationRegistry.getContext(),
RemoteMemoryIntArrayService.class));
synchronized (mLock) {
if (mRemoteInstance == null) {
bindLocked();
}
try {
mRemoteInstance.create(size, clientWritable);
} catch (RemoteException e) {
throw new IOException(e);
}
}
}
public MemoryIntArray peekInstance() {
try {
return mRemoteInstance.peekInstance();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
public boolean isWritable() {
try {
return mRemoteInstance.isWritable();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
public int get(int index) throws IOException {
try {
return mRemoteInstance.get(index);
} catch (RemoteException e) {
throw new IOException(e);
}
}
public void set(int index, int value) throws IOException {
try {
mRemoteInstance.set(index, value);
} catch (RemoteException e) {
throw new IOException(e);
}
}
public int size() throws IOException {
try {
return mRemoteInstance.size();
} catch (RemoteException e) {
throw new IOException(e);
}
}
public void close() {
try {
mRemoteInstance.close();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
public boolean isClosed() {
try {
return mRemoteInstance.isClosed();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
private void bindLocked() throws TimeoutException {
if (mRemoteInstance != null) {
return;
}
InstrumentationRegistry.getContext().bindService(mIntent, this, Context.BIND_AUTO_CREATE);
final long startMillis = SystemClock.uptimeMillis();
while (true) {
if (mRemoteInstance != null) {
break;
}
final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
final long remainingMillis = BIND_REMOTE_SERVICE_TIMEOUT - elapsedMillis;
if (remainingMillis <= 0) {
throw new TimeoutException("Cannot get spooler!");
}
try {
mLock.wait(remainingMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
mLock.notifyAll();
}
public void destroy() {
synchronized (mLock) {
if (mRemoteInstance == null) {
return;
}
mRemoteInstance = null;
InstrumentationRegistry.getContext().unbindService(this);
}
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mRemoteInstance = android.util.IRemoteMemoryIntArray.Stub.asInterface(service);
mLock.notifyAll();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
mRemoteInstance = null;
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.util;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import java.io.IOException;
/**
* Service to interact with a {@link MemoryIntArray} in another process.
*/
public class RemoteMemoryIntArrayService extends Service {
private final Object mLock = new Object();
private MemoryIntArray mArray;
@Override
public IBinder onBind(Intent intent) {
return new android.util.IRemoteMemoryIntArray.Stub() {
@Override
public void create(int size, boolean clientWritable) {
synchronized (mLock) {
try {
mArray = new MemoryIntArray(size, clientWritable);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public MemoryIntArray peekInstance() {
synchronized (mLock) {
return mArray;
}
}
@Override
public boolean isWritable() {
synchronized (mLock) {
return mArray.isWritable();
}
}
@Override
public int get(int index) {
synchronized (mLock) {
try {
return mArray.get(index);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public void set(int index, int value) {
synchronized (mLock) {
try {
mArray.set(index, value);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public int size() {
synchronized (mLock) {
try {
return mArray.size();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public void close() {
synchronized (mLock) {
try {
mArray.close();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
@Override
public boolean isClosed() {
synchronized (mLock) {
return mArray.isClosed();
}
}
};
}
}

View File

@@ -42,35 +42,25 @@ final class GenerationRegistry {
private final SparseIntArray mKeyToIndexMap = new SparseIntArray();
@GuardedBy("mLock")
private final MemoryIntArray mImpl;
private MemoryIntArray mBackingStore;
public GenerationRegistry(Object lock) {
mLock = lock;
// One for the global table, two for system and secure tables for a
// managed profile (managed profile is not included in the max user
// count), ten for partially deleted users if users are quickly removed,
// and twice max user count for system and secure.
final int size = 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
MemoryIntArray impl = null;
try {
impl = new MemoryIntArray(size, false);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error creating generation tracker", e);
}
mImpl = impl;
}
public void incrementGeneration(int key) {
synchronized (mLock) {
if (mImpl != null) {
MemoryIntArray backingStore = getBackingStoreLocked();
if (backingStore != null) {
try {
final int index = getKeyIndexLocked(key);
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
if (index >= 0) {
final int generation = mImpl.get(index) + 1;
mImpl.set(index, generation);
final int generation = backingStore.get(index) + 1;
backingStore.set(index, generation);
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error updating generation id", e);
destroyBackingStore();
}
}
}
@@ -78,34 +68,98 @@ final class GenerationRegistry {
public void addGenerationData(Bundle bundle, int key) {
synchronized (mLock) {
if (mImpl != null) {
final int index = getKeyIndexLocked(key);
if (index >= 0) {
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY, mImpl);
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
if (DEBUG) {
Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
MemoryIntArray backingStore = getBackingStoreLocked();
try {
if (backingStore != null) {
final int index = getKeyIndexLocked(key, mKeyToIndexMap, backingStore);
if (index >= 0) {
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
backingStore);
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
if (DEBUG) {
Slog.i(LOG_TAG, "Exported index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
}
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error adding generation data", e);
destroyBackingStore();
}
}
}
public void onUserRemoved(int userId) {
synchronized (mLock) {
MemoryIntArray backingStore = getBackingStoreLocked();
if (backingStore != null && mKeyToIndexMap.size() > 0) {
try {
final int secureKey = SettingsProvider.makeKey(
SettingsProvider.SETTINGS_TYPE_SECURE, userId);
resetSlotForKeyLocked(secureKey, mKeyToIndexMap, backingStore);
final int systemKey = SettingsProvider.makeKey(
SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
resetSlotForKeyLocked(systemKey, mKeyToIndexMap, backingStore);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error cleaning up for user", e);
destroyBackingStore();
}
}
}
}
private int getKeyIndexLocked(int key) {
int index = mKeyToIndexMap.get(key, -1);
private MemoryIntArray getBackingStoreLocked() {
if (mBackingStore == null) {
// One for the global table, two for system and secure tables for a
// managed profile (managed profile is not included in the max user
// count), ten for partially deleted users if users are quickly removed,
// and twice max user count for system and secure.
final int size = 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
try {
mBackingStore = new MemoryIntArray(size, false);
} catch (IOException e) {
Slog.e(LOG_TAG, "Error creating generation tracker", e);
}
}
return mBackingStore;
}
private void destroyBackingStore() {
if (mBackingStore != null) {
try {
mBackingStore.close();
} catch (IOException e) {
Slog.e(LOG_TAG, "Cannot close generation memory array", e);
}
mBackingStore = null;
}
}
private static void resetSlotForKeyLocked(int key, SparseIntArray keyToIndexMap,
MemoryIntArray backingStore) throws IOException {
final int index = keyToIndexMap.get(key, -1);
if (index >= 0) {
keyToIndexMap.delete(key);
backingStore.set(index, 0);
if (DEBUG) {
Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
}
}
private static int getKeyIndexLocked(int key, SparseIntArray keyToIndexMap,
MemoryIntArray backingStore) throws IOException {
int index = keyToIndexMap.get(key, -1);
if (index < 0) {
index = findNextEmptyIndex();
index = findNextEmptyIndex(backingStore);
if (index >= 0) {
try {
mImpl.set(index, 1);
mKeyToIndexMap.append(key, index);
if (DEBUG) {
Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Cannot write to generation memory array", e);
backingStore.set(index, 1);
keyToIndexMap.append(key, index);
if (DEBUG) {
Slog.i(LOG_TAG, "Allocated index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
} else {
Slog.e(LOG_TAG, "Could not allocate generation index");
@@ -114,47 +168,13 @@ final class GenerationRegistry {
return index;
}
public void onUserRemoved(int userId) {
synchronized (mLock) {
if (mImpl != null && mKeyToIndexMap.size() > 0) {
final int secureKey = SettingsProvider.makeKey(
SettingsProvider.SETTINGS_TYPE_SECURE, userId);
resetSlotForKeyLocked(secureKey);
final int systemKey = SettingsProvider.makeKey(
SettingsProvider.SETTINGS_TYPE_SYSTEM, userId);
resetSlotForKeyLocked(systemKey);
private static int findNextEmptyIndex(MemoryIntArray backingStore) throws IOException {
final int size = backingStore.size();
for (int i = 0; i < size; i++) {
if (backingStore.get(i) == 0) {
return i;
}
}
}
private void resetSlotForKeyLocked(int key) {
final int index = mKeyToIndexMap.get(key, -1);
if (index >= 0) {
mKeyToIndexMap.delete(key);
try {
mImpl.set(index, 0);
if (DEBUG) {
Slog.i(LOG_TAG, "Freed index:" + index + " for key:"
+ SettingsProvider.keyToString(key));
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Cannot write to generation memory array", e);
}
}
}
private int findNextEmptyIndex() {
try {
final int size = mImpl.size();
for (int i = 0; i < size; i++) {
if (mImpl.get(i) == 0) {
return i;
}
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Error reading generation memory array", e);
}
return -1;
}
}