Merge "Run framework migration on a background thread." into sc-dev

This commit is contained in:
Alexander Dorokhine
2021-03-29 21:54:51 +00:00
committed by Android (Google) Code Review

View File

@@ -32,13 +32,11 @@ import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -158,68 +156,16 @@ public final class AppSearchSession implements Closeable {
schemasPackageAccessibleBundles, schemasPackageAccessibleBundles,
callbackExecutor, callbackExecutor,
callback); callback);
return; } else {
} setSchemaWithMigrations(
request,
try {
// Migration process
// 1. Generate the current and the final version map.
// TODO(b/182855402) Release binder thread and move the heavy work into worker thread.
AndroidFuture<AppSearchResult<GetSchemaResponse>> future = new AndroidFuture<>();
getSchema(callbackExecutor, future::complete);
AppSearchResult<GetSchemaResponse> getSchemaResult = future.get();
if (!getSchemaResult.isSuccess()) {
callback.accept(AppSearchResult.newFailedResult(getSchemaResult));
return;
}
GetSchemaResponse getSchemaResponse = getSchemaResult.getResultValue();
Set<AppSearchSchema> currentSchemas = getSchemaResponse.getSchemas();
Map<String, Integer> currentVersionMap =
SchemaMigrationUtil.buildVersionMap(currentSchemas,
getSchemaResponse.getVersion());
Map<String, Integer> finalVersionMap =
SchemaMigrationUtil.buildVersionMap(request.getSchemas(), request.getVersion());
// 2. SetSchema with forceOverride=false, to retrieve the list of incompatible/deleted
// types.
mService.setSchema(
mPackageName,
mDatabaseName,
schemaBundles, schemaBundles,
new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
schemasPackageAccessibleBundles, schemasPackageAccessibleBundles,
/*forceOverride=*/ false, workExecutor,
mUserId, callbackExecutor,
request.getVersion(), callback);
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
callbackExecutor.execute(() -> {
if (result.isSuccess()) {
// TODO(b/183177268): once migration is implemented, run
// it on workExecutor.
try {
Bundle bundle = (Bundle) result.getResultValue();
SetSchemaResponse setSchemaResponse =
new SetSchemaResponse(bundle);
setSchemaMigration(
request, setSchemaResponse, schemaBundles,
schemasPackageAccessibleBundles, currentVersionMap,
finalVersionMap, callback);
} catch (Throwable t) {
callback.accept(AppSearchResult.throwableToFailedResult(t));
}
} else {
callback.accept(result);
}
});
}
});
mIsMutated = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (Throwable t) {
callback.accept(AppSearchResult.throwableToFailedResult(t));
} }
mIsMutated = true;
} }
/** /**
@@ -667,7 +613,8 @@ public final class AppSearchSession implements Closeable {
* <p>We only need one time {@link #setSchema} call for no-migration scenario by using the * <p>We only need one time {@link #setSchema} call for no-migration scenario by using the
* forceoverride in the request. * forceoverride in the request.
*/ */
private void setSchemaNoMigrations(@NonNull SetSchemaRequest request, private void setSchemaNoMigrations(
@NonNull SetSchemaRequest request,
@NonNull List<Bundle> schemaBundles, @NonNull List<Bundle> schemaBundles,
@NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles,
@NonNull @CallbackExecutor Executor executor, @NonNull @CallbackExecutor Executor executor,
@@ -709,7 +656,6 @@ public final class AppSearchSession implements Closeable {
}); });
} }
}); });
mIsMutated = true;
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();
} }
@@ -721,86 +667,144 @@ public final class AppSearchSession implements Closeable {
* <p>First time {@link #setSchema} call with forceOverride is false gives us all incompatible * <p>First time {@link #setSchema} call with forceOverride is false gives us all incompatible
* changes. After trigger migrations, the second time call {@link #setSchema} will actually * changes. After trigger migrations, the second time call {@link #setSchema} will actually
* apply the changes. * apply the changes.
*
* @param setSchemaResponse the result of the first setSchema call with forceOverride=false.
*/ */
private void setSchemaMigration(@NonNull SetSchemaRequest request, private void setSchemaWithMigrations(
@NonNull SetSchemaResponse setSchemaResponse, @NonNull SetSchemaRequest request,
@NonNull List<Bundle> schemaBundles, @NonNull List<Bundle> schemaBundles,
@NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles,
@NonNull Map<String, Integer> currentVersionMap, Map<String, Integer> finalVersionMap, @NonNull Executor workExecutor,
@NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) @NonNull @CallbackExecutor Executor callbackExecutor,
throws AppSearchException, IOException, RemoteException, ExecutionException, @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
InterruptedException { workExecutor.execute(() -> {
// 1. If forceOverride is false, check that all incompatible types will be migrated. try {
// If some aren't we must throw an error, rather than proceeding and deleting those // Migration process
// types. // 1. Generate the current and the final version map.
if (!request.isForceOverride()) { AndroidFuture<AppSearchResult<GetSchemaResponse>> getSchemaFuture =
Set<String> unmigratedTypes = SchemaMigrationUtil.getUnmigratedIncompatibleTypes( new AndroidFuture<>();
setSchemaResponse.getIncompatibleTypes(), getSchema(callbackExecutor, getSchemaFuture::complete);
request.getMigrators(), AppSearchResult<GetSchemaResponse> getSchemaResult = getSchemaFuture.get();
currentVersionMap, if (!getSchemaResult.isSuccess()) {
finalVersionMap); callbackExecutor.execute(() ->
// check if there are any unmigrated types or deleted types. If there are, we will throw callback.accept(AppSearchResult.newFailedResult(getSchemaResult)));
// an exception. return;
// Since the force override is false, the schema will not have been set if there are any
// incompatible or deleted types.
checkDeletedAndIncompatible(setSchemaResponse.getDeletedTypes(),
unmigratedTypes);
}
try (AppSearchMigrationHelper migrationHelper =
new AppSearchMigrationHelper(mService, mUserId, currentVersionMap,
finalVersionMap, mPackageName, mDatabaseName)) {
Map<String, Migrator> migratorMap = request.getMigrators();
// 2. Trigger migration for all migrators.
// TODO(b/177266929) trigger migration for all types together rather than separately.
Set<String> migratedTypes = new ArraySet<>();
for (Map.Entry<String, Migrator> entry : migratorMap.entrySet()) {
String schemaType = entry.getKey();
Migrator migrator = entry.getValue();
if (SchemaMigrationUtil.shouldTriggerMigration(
schemaType, migrator, currentVersionMap, finalVersionMap)) {
migrationHelper.queryAndTransform(schemaType, migrator);
migratedTypes.add(schemaType);
} }
} GetSchemaResponse getSchemaResponse = getSchemaResult.getResultValue();
Set<AppSearchSchema> currentSchemas = getSchemaResponse.getSchemas();
Map<String, Integer> currentVersionMap = SchemaMigrationUtil.buildVersionMap(
currentSchemas, getSchemaResponse.getVersion());
Map<String, Integer> finalVersionMap = SchemaMigrationUtil.buildVersionMap(
request.getSchemas(), request.getVersion());
// 3. SetSchema a second time with forceOverride=true if the first attempted failed. // 2. SetSchema with forceOverride=false, to retrieve the list of
if (!setSchemaResponse.getIncompatibleTypes().isEmpty() // incompatible/deleted types.
|| !setSchemaResponse.getDeletedTypes().isEmpty()) { AndroidFuture<AppSearchResult<Bundle>> setSchemaFuture = new AndroidFuture<>();
AndroidFuture<AppSearchResult<SetSchemaResponse>> future = new AndroidFuture<>();
// only trigger second setSchema() call if the first one is fail.
mService.setSchema( mService.setSchema(
mPackageName, mPackageName,
mDatabaseName, mDatabaseName,
schemaBundles, schemaBundles,
new ArrayList<>(request.getSchemasNotDisplayedBySystem()), new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
schemasPackageAccessibleBundles, schemasPackageAccessibleBundles,
/*forceOverride=*/ true, /*forceOverride=*/ false,
mUserId, mUserId,
request.getVersion(), request.getVersion(),
new IAppSearchResultCallback.Stub() { new IAppSearchResultCallback.Stub() {
@Override public void onResult(AppSearchResult result) {
public void onResult(AppSearchResult result) throws RemoteException { setSchemaFuture.complete(result);
future.complete(result);
} }
}); });
AppSearchResult<SetSchemaResponse> secondSetSchemaResult = future.get(); AppSearchResult<Bundle> setSchemaResult = setSchemaFuture.get();
if (!secondSetSchemaResult.isSuccess()) { if (!setSchemaResult.isSuccess()) {
// we failed to set the schema in second time with force override = true, which callbackExecutor.execute(() ->
// is an impossible case. Since we only swallow the incompatible error in the callback.accept(AppSearchResult.newFailedResult(setSchemaResult)));
// first setSchema call, all other errors will be thrown at the first time.
callback.accept(secondSetSchemaResult);
return; return;
} }
} SetSchemaResponse setSchemaResponse =
new SetSchemaResponse(setSchemaResult.getResultValue());
SetSchemaResponse.Builder responseBuilder = setSchemaResponse.toBuilder() // 1. If forceOverride is false, check that all incompatible types will be migrated.
.addMigratedTypes(migratedTypes); // If some aren't we must throw an error, rather than proceeding and deleting those
callback.accept(migrationHelper.putMigratedDocuments(responseBuilder)); // types.
} if (!request.isForceOverride()) {
Set<String> unmigratedTypes =
SchemaMigrationUtil.getUnmigratedIncompatibleTypes(
setSchemaResponse.getIncompatibleTypes(),
request.getMigrators(),
currentVersionMap,
finalVersionMap);
// check if there are any unmigrated types or deleted types. If there are, we
// will throw an exception.
// Since the force override is false, the schema will not have been set if there
// are any incompatible or deleted types.
checkDeletedAndIncompatible(
setSchemaResponse.getDeletedTypes(), unmigratedTypes);
}
try (AppSearchMigrationHelper migrationHelper =
new AppSearchMigrationHelper(
mService, mUserId, currentVersionMap, finalVersionMap,
mPackageName, mDatabaseName)) {
Map<String, Migrator> migratorMap = request.getMigrators();
// 2. Trigger migration for all migrators.
// TODO(b/177266929) trigger migration for all types together rather than
// separately.
Set<String> migratedTypes = new ArraySet<>();
for (Map.Entry<String, Migrator> entry : migratorMap.entrySet()) {
String schemaType = entry.getKey();
Migrator migrator = entry.getValue();
if (SchemaMigrationUtil.shouldTriggerMigration(
schemaType, migrator, currentVersionMap, finalVersionMap)) {
migrationHelper.queryAndTransform(schemaType, migrator);
migratedTypes.add(schemaType);
}
}
// 3. SetSchema a second time with forceOverride=true if the first attempted
// failed.
if (!setSchemaResponse.getIncompatibleTypes().isEmpty()
|| !setSchemaResponse.getDeletedTypes().isEmpty()) {
AndroidFuture<AppSearchResult<Bundle>> setSchema2Future =
new AndroidFuture<>();
// only trigger second setSchema() call if the first one is fail.
mService.setSchema(
mPackageName,
mDatabaseName,
schemaBundles,
new ArrayList<>(request.getSchemasNotDisplayedBySystem()),
schemasPackageAccessibleBundles,
/*forceOverride=*/ true,
request.getVersion(),
mUserId,
new IAppSearchResultCallback.Stub() {
@Override
public void onResult(AppSearchResult result) {
setSchema2Future.complete(result);
}
});
AppSearchResult<Bundle> setSchema2Result = setSchema2Future.get();
if (!setSchema2Result.isSuccess()) {
// we failed to set the schema in second time with forceOverride = true,
// which is an impossible case. Since we only swallow the incompatible
// error in the first setSchema call, all other errors will be thrown at
// the first time.
callbackExecutor.execute(() -> callback.accept(
AppSearchResult.newFailedResult(setSchemaResult)));
return;
}
}
SetSchemaResponse.Builder responseBuilder = setSchemaResponse.toBuilder()
.addMigratedTypes(migratedTypes);
AppSearchResult<SetSchemaResponse> putResult =
migrationHelper.putMigratedDocuments(responseBuilder);
callbackExecutor.execute(() -> callback.accept(putResult));
}
} catch (Throwable t) {
callbackExecutor.execute(() -> callback.accept(
AppSearchResult.throwableToFailedResult(t)));
}
});
} }
/** Checks the setSchema() call won't delete any types or has incompatible types. */ /** Checks the setSchema() call won't delete any types or has incompatible types. */