Skip to content

Commit

Permalink
Defer native module init
Browse files Browse the repository at this point in the history
  • Loading branch information
bidetofevil committed Mar 11, 2024
1 parent d6d0238 commit 94eb7bf
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.anr.ndk
import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.config.behavior.AnrBehavior
import io.embrace.android.embracesdk.internal.DeviceArchitecture
import io.embrace.android.embracesdk.internal.SharedObjectLoader
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger
import io.embrace.android.embracesdk.payload.NativeThreadAnrInterval
Expand All @@ -25,7 +26,8 @@ internal class EmbraceNativeThreadSamplerService @JvmOverloads constructor(
private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger,
private val delegate: NdkDelegate = NativeThreadSamplerNdkDelegate(),
private val scheduledWorker: ScheduledWorker,
private val deviceArchitecture: DeviceArchitecture
private val deviceArchitecture: DeviceArchitecture,
private val sharedObjectLoader: SharedObjectLoader
) : NativeThreadSamplerService {

companion object {
Expand Down Expand Up @@ -55,11 +57,16 @@ internal class EmbraceNativeThreadSamplerService @JvmOverloads constructor(
private var targetThread: Thread = Thread.currentThread()

override fun setupNativeSampler(): Boolean {
logger.logDeveloper(
"EmbraceNativeThreadSamplerService",
"Target thread found, attempting to install NativeThreadSampler"
)
return delegate.setupNativeThreadSampler(deviceArchitecture.is32BitDevice)
return if (sharedObjectLoader.loadEmbraceNative()) {
logger.logDeveloper(
"EmbraceNativeThreadSamplerService",
"Target thread found, attempting to install NativeThreadSampler"
)
delegate.setupNativeThreadSampler(deviceArchitecture.is32BitDevice)
} else {
logger.logWarning("Embrace native binary load failed. Native thread sampler setup aborted.")
false
}
}

override fun monitorCurrentThread(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.os.Handler
import android.os.Looper
import io.embrace.android.embracesdk.anr.AnrService
import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.internal.SharedObjectLoader
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger
import io.embrace.android.embracesdk.payload.NativeThreadAnrSample
Expand All @@ -17,9 +18,9 @@ internal class NativeThreadSamplerNdkDelegate : EmbraceNativeThreadSamplerServic
}

internal class NativeThreadSamplerInstaller(
private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger
private val sharedObjectLoader: SharedObjectLoader,
private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger,
) {

private val isMonitoring = AtomicBoolean(false)
private var targetHandler: Handler? = null

Expand Down Expand Up @@ -64,6 +65,9 @@ internal class NativeThreadSamplerInstaller(
}

currentThread = Thread.currentThread()
if (!sharedObjectLoader.loadEmbraceNative()) {
return
}
prepareTargetHandler()

if (configService.anrBehavior.isNativeThreadAnrSamplingEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
package io.embrace.android.embracesdk.internal

import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger
import java.util.concurrent.atomic.AtomicBoolean

/**
* Loads shared object files.
* Component to load native binaries
*/
internal class SharedObjectLoader {
val loaded = AtomicBoolean(false)

fun loadEmbraceNative() = try {
Systrace.traceSynchronous("load-embrace-native-lib") {
System.loadLibrary("embrace-native")
/**
* Load Embrace native binary if necessary
*/
fun loadEmbraceNative(): Boolean {
if (loaded.get()) {
return true
}
synchronized(loaded) {
if (!loaded.get()) {
try {
Systrace.traceSynchronous("load-embrace-native-lib") {
System.loadLibrary("embrace-native")
}
loaded.set(true)
} catch (exc: UnsatisfiedLinkError) {
InternalStaticEmbraceLogger.logError("Failed to load SO file embrace-native", exc)
return false
}
}

return true
}
true
} catch (exc: UnsatisfiedLinkError) {
InternalStaticEmbraceLogger.logError("Failed to load SO file embrace-native", exc)
false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import io.embrace.android.embracesdk.injection.DeliveryModule
import io.embrace.android.embracesdk.injection.EssentialServiceModule
import io.embrace.android.embracesdk.injection.StorageModule
import io.embrace.android.embracesdk.injection.singleton
import io.embrace.android.embracesdk.internal.SharedObjectLoader
import io.embrace.android.embracesdk.internal.Systrace
import io.embrace.android.embracesdk.session.properties.EmbraceSessionProperties
import io.embrace.android.embracesdk.worker.WorkerName
Expand Down Expand Up @@ -59,12 +58,13 @@ internal class NativeModuleImpl(

override val nativeThreadSamplerService: NativeThreadSamplerService? by singleton {
Systrace.traceSynchronous("native-thread-sampler-init") {
if (nativeThreadSamplingEnabled(essentialServiceModule.configService, essentialServiceModule.sharedObjectLoader)) {
if (nativeThreadSamplingEnabled(essentialServiceModule.configService)) {
EmbraceNativeThreadSamplerService(
essentialServiceModule.configService,
lazy { ndkService.getSymbolsForCurrentArch() },
scheduledWorker = workerThreadModule.scheduledWorker(WorkerName.BACKGROUND_REGISTRATION),
deviceArchitecture = essentialServiceModule.deviceArchitecture
deviceArchitecture = essentialServiceModule.deviceArchitecture,
sharedObjectLoader = essentialServiceModule.sharedObjectLoader
)
} else {
null
Expand All @@ -74,16 +74,15 @@ internal class NativeModuleImpl(

override val nativeThreadSamplerInstaller: NativeThreadSamplerInstaller? by singleton {
Systrace.traceSynchronous("native-thread-sampler-installer-init") {
if (nativeThreadSamplingEnabled(essentialServiceModule.configService, essentialServiceModule.sharedObjectLoader)) {
NativeThreadSamplerInstaller()
if (nativeThreadSamplingEnabled(essentialServiceModule.configService)) {
NativeThreadSamplerInstaller(sharedObjectLoader = essentialServiceModule.sharedObjectLoader)
} else {
null
}
}
}

private fun nativeThreadSamplingEnabled(configService: ConfigService, sharedObjectLoader: SharedObjectLoader) =
configService.autoDataCaptureBehavior.isNdkEnabled() && sharedObjectLoader.loadEmbraceNative()
private fun nativeThreadSamplingEnabled(configService: ConfigService) = configService.autoDataCaptureBehavior.isNdkEnabled()

private val embraceNdkServiceRepository by singleton {
EmbraceNdkServiceRepository(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import io.embrace.android.embracesdk.config.remote.AnrRemoteConfig
import io.embrace.android.embracesdk.fakes.FakeConfigService
import io.embrace.android.embracesdk.fakes.FakeDeviceArchitecture
import io.embrace.android.embracesdk.fakes.fakeAnrBehavior
import io.embrace.android.embracesdk.internal.SharedObjectLoader
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.payload.NativeThreadAnrInterval
import io.embrace.android.embracesdk.payload.NativeThreadAnrSample
Expand All @@ -32,6 +33,7 @@ import java.util.concurrent.ThreadFactory
internal class EmbraceNativeThreadSamplerServiceTest {

private lateinit var sampler: EmbraceNativeThreadSamplerService
private lateinit var sharedObjectLoader: SharedObjectLoader
private lateinit var configService: ConfigService
private lateinit var delegate: EmbraceNativeThreadSamplerService.NdkDelegate
private lateinit var random: Random
Expand All @@ -46,6 +48,7 @@ internal class EmbraceNativeThreadSamplerServiceTest {
cfg = AnrRemoteConfig(pctNativeThreadAnrSamplingEnabled = 100f)
anrBehavior = fakeAnrBehavior { cfg }
configService = FakeConfigService(anrBehavior = anrBehavior)
sharedObjectLoader = mockk(relaxed = true)
delegate = mockk(relaxed = true)
val logger = InternalEmbraceLogger()
random = mockk(relaxed = true)
Expand All @@ -58,9 +61,11 @@ internal class EmbraceNativeThreadSamplerServiceTest {
logger,
delegate,
ScheduledWorker(executorService),
FakeDeviceArchitecture()
FakeDeviceArchitecture(),
sharedObjectLoader
)
every { random.nextInt(any()) } returns 0
every { sharedObjectLoader.loadEmbraceNative() } returns true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.embrace.android.embracesdk.config.ConfigService
import io.embrace.android.embracesdk.config.remote.AnrRemoteConfig
import io.embrace.android.embracesdk.fakes.FakeConfigService
import io.embrace.android.embracesdk.fakes.fakeAnrBehavior
import io.embrace.android.embracesdk.internal.SharedObjectLoader
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
Expand All @@ -19,31 +20,34 @@ import java.util.concurrent.TimeUnit
internal class NativeThreadSamplerInstallerTest {

private lateinit var sampler: EmbraceNativeThreadSamplerService
private lateinit var sharedObjectLoader: SharedObjectLoader
private lateinit var configService: ConfigService
private lateinit var anrService: AnrService
private lateinit var delegate: EmbraceNativeThreadSamplerService.NdkDelegate
private lateinit var cfg: AnrRemoteConfig
private lateinit var installer: NativeThreadSamplerInstaller

@Before
fun setUp() {
sharedObjectLoader = mockk(relaxed = true)
anrService = mockk(relaxed = true)
delegate = mockk(relaxed = true)
sampler = mockk(relaxed = true)

cfg = AnrRemoteConfig(pctNativeThreadAnrSamplingEnabled = 100f)
configService = FakeConfigService(anrBehavior = fakeAnrBehavior { cfg })
installer = NativeThreadSamplerInstaller(sharedObjectLoader = sharedObjectLoader)
every { sharedObjectLoader.loadEmbraceNative() } returns true
}

@Test
fun testInstallDisabled() {
val installer = NativeThreadSamplerInstaller()
installer.monitorCurrentThread(sampler, configService, anrService)
verify(exactly = 0) { delegate.setupNativeThreadSampler(false) }
}

@Test
fun testInstallEnabledSuccess() {
val installer = NativeThreadSamplerInstaller()
every { sampler.setupNativeSampler() } returns true
every { sampler.monitorCurrentThread() } returns true

Expand All @@ -56,7 +60,6 @@ internal class NativeThreadSamplerInstallerTest {

@Test
fun testInstallEnabledFailure() {
val installer = NativeThreadSamplerInstaller()
every { sampler.setupNativeSampler() } returns false
every { sampler.monitorCurrentThread() } returns false
sampler.setupNativeSampler()
Expand All @@ -81,7 +84,6 @@ internal class NativeThreadSamplerInstallerTest {

@Test
fun testConfigListener() {
val installer = NativeThreadSamplerInstaller()
every { sampler.setupNativeSampler() } returns true
every { sampler.monitorCurrentThread() } returns true
sampler.setupNativeSampler()
Expand All @@ -95,7 +97,6 @@ internal class NativeThreadSamplerInstallerTest {

@Test
fun testInstallNewThread() {
val installer = NativeThreadSamplerInstaller()
every { sampler.setupNativeSampler() } returns true
every { sampler.monitorCurrentThread() } returns true

Expand Down

0 comments on commit 94eb7bf

Please sign in to comment.