Skip to content

Commit

Permalink
introduce Observer interface
Browse files Browse the repository at this point in the history
Summary: This diff adds `Observer` interface, and `ObserverHolder`, a static holder for an array of `Observer`s that dispatches calls to all of the `Obsever`s in the array. SoLoader uses `ObserverHolder` to notify all `Observer`s of library loading progress.

Reviewed By: adicatana, danjin250

Differential Revision: D48040740

fbshipit-source-id: 8963a48749670504d71f894436dab6e9728cd4d8
  • Loading branch information
michalgr authored and facebook-github-bot committed Jan 25, 2024
1 parent 87947ff commit ad4dcc1
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 37 deletions.
1 change: 1 addition & 0 deletions java/com/facebook/soloader/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fb_core_android_library(
LOADER_SRCS = glob(
[
"*.java",
"observer/*.java",
"recovery/*.java",
],
exclude = [
Expand Down
13 changes: 12 additions & 1 deletion java/com/facebook/soloader/NativeDeps.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package com.facebook.soloader;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.StrictMode;
import com.facebook.soloader.observer.ObserverHolder;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -66,19 +68,28 @@ public static void loadDependencies(
}
}

@SuppressLint({"CatchGeneralException", "EmptyCatchBlock"})
public static String[] getDependencies(String soName, ElfByteChannel bc) throws IOException {
@Nullable Throwable failure = null;
if (SoLoader.SYSTRACE_LIBRARY_LOADING) {
Api18TraceUtils.beginTraceSection("soloader.NativeDeps.getDependencies[", soName, "]");
}
ObserverHolder.onGetDependenciesStart();
try {
String[] deps = awaitGetDepsFromPrecomputedDeps(soName);
if (deps != null) {
return deps;
}
return MinElf.extract_DT_NEEDED(bc);
} catch (MinElf.ElfError err) {
throw SoLoaderULErrorFactory.create(soName, err);
UnsatisfiedLinkError ule = SoLoaderULErrorFactory.create(soName, err);
failure = ule;
throw ule;
} catch (Error | RuntimeException t) {
failure = t;
throw t;
} finally {
ObserverHolder.onGetDependenciesEnd(failure);
if (SoLoader.SYSTRACE_LIBRARY_LOADING) {
Api18TraceUtils.endSection();
}
Expand Down
135 changes: 99 additions & 36 deletions java/com/facebook/soloader/SoLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import android.text.TextUtils;
import com.facebook.soloader.nativeloader.NativeLoader;
import com.facebook.soloader.nativeloader.SystemDelegate;
import com.facebook.soloader.observer.ObserverHolder;
import com.facebook.soloader.recovery.DefaultRecoveryStrategyFactory;
import com.facebook.soloader.recovery.RecoveryStrategy;
import com.facebook.soloader.recovery.RecoveryStrategyFactory;
Expand Down Expand Up @@ -616,6 +617,7 @@ static class TestOnlyUtils {
sSoFileLoader = null;
sApplicationContext = null;
sRecoveryStrategyFactory = null;
ObserverHolder.resetObserversForTestsOnly();
}
setSoSources(null);
}
Expand Down Expand Up @@ -771,12 +773,24 @@ public static boolean loadLibrary(String shortName, int loadFlags) throws Unsati
return true;
}

String mergedLibName = MergedSoMapping.mapLibName(shortName);

String soName = mergedLibName != null ? mergedLibName : shortName;
return loadLibraryOnAndroid(shortName, loadFlags);
}

return loadLibraryBySoName(
System.mapLibraryName(soName), shortName, mergedLibName, loadFlags, null);
@SuppressLint({"CatchGeneralException", "EmptyCatchBlock"})
private static boolean loadLibraryOnAndroid(String shortName, int loadFlags) {
@Nullable Throwable failure = null;
ObserverHolder.onLoadLibraryStart(shortName, loadFlags);
try {
String mergedLibName = MergedSoMapping.mapLibName(shortName);
String soName = mergedLibName != null ? mergedLibName : shortName;
return loadLibraryBySoName(
System.mapLibraryName(soName), shortName, mergedLibName, loadFlags, null);
} catch (Throwable t) {
failure = t;
throw t;
} finally {
ObserverHolder.onLoadLibraryEnd(failure);
}
}

private static @Nullable Boolean loadLibraryOnNonAndroid(String shortName) {
Expand Down Expand Up @@ -821,10 +835,20 @@ public static boolean loadLibrary(String shortName, int loadFlags) throws Unsati
* @param loadFlags
* @param oldPolicy
*/
@SuppressLint({"CatchGeneralException", "EmptyCatchBlock"})
/* package */ static void loadDependency(
String soName, int loadFlags, StrictMode.ThreadPolicy oldPolicy) {
loadLibraryBySoNameImpl(
soName, null, null, loadFlags | SoSource.LOAD_FLAG_ALLOW_IMPLICIT_PROVISION, oldPolicy);
@Nullable Throwable failure = null;
ObserverHolder.onLoadDependencyStart(soName, loadFlags);
try {
loadLibraryBySoNameImpl(
soName, null, null, loadFlags | SoSource.LOAD_FLAG_ALLOW_IMPLICIT_PROVISION, oldPolicy);
} catch (Throwable t) {
failure = t;
throw t;
} finally {
ObserverHolder.onLoadDependencyEnd(failure);
}
}

private static boolean loadLibraryBySoName(
Expand All @@ -838,37 +862,60 @@ private static boolean loadLibraryBySoName(
try {
return loadLibraryBySoNameImpl(soName, shortName, mergedLibName, loadFlags, oldPolicy);
} catch (UnsatisfiedLinkError e) {
LogUtil.w(TAG, "Starting recovery for " + soName, e);
sSoSourcesLock.writeLock().lock();
try {
if (recovery == null) {
recovery = getRecoveryStrategy();
}
if (recovery != null && recovery.recover(e, sSoSources)) {
sSoSourcesVersion.getAndIncrement();
LogUtil.w(TAG, "Attempting to load library again");
continue;
}
} catch (NoBaseApkException noBaseApkException) {
// If we failed during recovery, we only want to throw the recovery exception for the case
// when the base APK path does not exist, everything else should preserve the initial
// error.
LogUtil.e(TAG, "Base APK not found during recovery", noBaseApkException);
throw noBaseApkException;
} catch (Exception recoveryException) {
LogUtil.e(
TAG,
"Got an exception during recovery, will throw the initial error instead",
recoveryException);
recovery = recover(soName, e, recovery);
}
}
}

@SuppressLint("CatchGeneralException")
private static RecoveryStrategy recover(
String soName, UnsatisfiedLinkError e, @Nullable RecoveryStrategy recovery) {
LogUtil.w(TAG, "Starting recovery for " + soName, e);
sSoSourcesLock.writeLock().lock();
try {
if (recovery == null) {
recovery = getRecoveryStrategy();
if (recovery == null) {
LogUtil.w(TAG, "No recovery strategy");
throw e;
} finally {
sSoSourcesLock.writeLock().unlock();
}

// No recovery mechanism worked, throwing initial error
LogUtil.w(TAG, "Failed to recover");
throw e;
}
if (recoverLocked(e, recovery)) {
sSoSourcesVersion.getAndIncrement();
return recovery;
}
} catch (NoBaseApkException noBaseApkException) {
// If we failed during recovery, we only want to throw the recovery exception for the case
// when the base APK path does not exist, everything else should preserve the initial
// error.
LogUtil.e(TAG, "Base APK not found during recovery", noBaseApkException);
throw noBaseApkException;
} catch (Exception recoveryException) {
LogUtil.e(
TAG,
"Got an exception during recovery, will throw the initial error instead",
recoveryException);
throw e;
} finally {
sSoSourcesLock.writeLock().unlock();
}

// No recovery mechanism worked, throwing initial error
LogUtil.w(TAG, "Failed to recover");
throw e;
}

@SuppressLint({"CatchGeneralException", "EmptyCatchBlock"})
private static boolean recoverLocked(UnsatisfiedLinkError e, RecoveryStrategy recovery) {
@Nullable Throwable failure = null;
ObserverHolder.onRecoveryStart(recovery);
try {
return recovery.recover(e, sSoSources);
} catch (Throwable t) {
failure = t;
throw t;
} finally {
ObserverHolder.onRecoveryEnd(failure);
}
}

Expand Down Expand Up @@ -1057,7 +1104,7 @@ private static void doLoadLibraryBySoName(
sSoSourcesLock.readLock().lock();
try {
for (SoSource source : sSoSources) {
if (source.loadLibrary(soName, loadFlags, oldPolicy) != SoSource.LOAD_RESULT_NOT_FOUND) {
if (loadLibraryFromSoSource(source, soName, loadFlags, oldPolicy)) {
return;
}
}
Expand All @@ -1083,6 +1130,22 @@ private static void doLoadLibraryBySoName(
}
}

@SuppressLint({"CatchGeneralException", "EmptyCatchBlock", "MissingSoLoaderLibrary"})
private static boolean loadLibraryFromSoSource(
SoSource source, String name, int loadFlags, StrictMode.ThreadPolicy oldPolicy)
throws IOException {
@Nullable Throwable failure = null;
ObserverHolder.onSoSourceLoadLibraryStart(source);
try {
return source.loadLibrary(name, loadFlags, oldPolicy) != SoSource.LOAD_RESULT_NOT_FOUND;
} catch (Throwable t) {
failure = t;
throw t;
} finally {
ObserverHolder.onSoSourceLoadLibraryEnd(failure);
}
}

/* package */ static File unpackLibraryBySoName(String soName) throws IOException {
sSoSourcesLock.readLock().lock();
try {
Expand Down
43 changes: 43 additions & 0 deletions java/com/facebook/soloader/observer/Observer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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 com.facebook.soloader.observer;

import com.facebook.soloader.SoSource;
import com.facebook.soloader.recovery.RecoveryStrategy;
import javax.annotation.Nullable;

public interface Observer {
void onLoadLibraryStart(String library, int flags);

void onLoadLibraryEnd(@Nullable Throwable t);

void onLoadDependencyStart(String library, int flags);

void onLoadDependencyEnd(@Nullable Throwable t);

void onSoSourceLoadLibraryStart(SoSource source);

void onSoSourceLoadLibraryEnd(@Nullable Throwable t);

void onRecoveryStart(RecoveryStrategy recovery);

void onRecoveryEnd(@Nullable Throwable t);

void onGetDependenciesStart();

void onGetDependenciesEnd(@Nullable Throwable t);
}
Loading

0 comments on commit ad4dcc1

Please sign in to comment.