Skip to content

Commit fbe0408

Browse files
natiginfopengdev
authored andcommitted
[maps-android] CPU affinity onsite fixes (#5404)
This PR includes 2 fixes: * Executing `mapboxRenderer.resetThreadServiceType()` from main thread since function should be called from owning thread. * Exposing `MapView.scheduleThreadServiceTypeReset()`. cc @mapbox/maps-android --------- Co-authored-by: Peng Liu <[email protected]> GitOrigin-RevId: cd5a7770e4081b9d271e500fb75d5d5ff079542c
1 parent 94bdf25 commit fbe0408

File tree

7 files changed

+66
-9
lines changed

7 files changed

+66
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ Mapbox welcomes participation and contributions from everyone.
99

1010

1111
# 11.14.0
12+
## Features ✨ and improvements 🏁
13+
* Added experimental `MapView.scheduleThreadServiceTypeReset()` to reset the renderer thread service type to Interactive. This experimental API is intended for edge cases involving custom lifecycle management or specific scenarios where the default lifecycle behavior is insufficient.
14+
1215
## Bug fixes 🐞
1316
* [compose] Avoid excessive debug logging on SourceState, which can result in Out Of Memory in extreme cases. Avoid appending geojson data in `GeoJsonSourceState.toString()` override.
1417

maps-sdk/api/Release/metalava.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ package com.mapbox.maps {
136136
method public final void removePlugin(String id);
137137
method public void removeRendererSetupErrorListener(com.mapbox.maps.renderer.RendererSetupErrorListener rendererSetupErrorListener);
138138
method @com.mapbox.maps.MapboxExperimental public boolean removeWidget(com.mapbox.maps.renderer.widget.Widget widget);
139+
method @com.mapbox.maps.MapboxDelicateApi @com.mapbox.maps.MapboxExperimental public final void scheduleThreadServiceTypeReset();
139140
method public final void setDebugOptions(java.util.Set<com.mapbox.maps.debugoptions.MapViewDebugOptions>);
140141
method public void setMaximumFps(@IntRange(from=1L, to=kotlin.jvm.internal.IntCompanionObject.MAX_VALUE.toLong()) int fps);
141142
method public void setOnFpsChangedListener(com.mapbox.maps.renderer.OnFpsChangedListener listener);

maps-sdk/api/maps-sdk.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ public class com/mapbox/maps/MapView : android/widget/FrameLayout, com/mapbox/ma
138138
public final fun removePlugin (Ljava/lang/String;)V
139139
public fun removeRendererSetupErrorListener (Lcom/mapbox/maps/renderer/RendererSetupErrorListener;)V
140140
public fun removeWidget (Lcom/mapbox/maps/renderer/widget/Widget;)Z
141+
public final fun scheduleThreadServiceTypeReset ()V
141142
public final fun setDebugOptions (Ljava/util/Set;)V
142143
public fun setMaximumFps (I)V
143144
public fun setOnFpsChangedListener (Lcom/mapbox/maps/renderer/OnFpsChangedListener;)V

maps-sdk/src/main/java/com/mapbox/maps/MapView.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,21 @@ open class MapView : FrameLayout, MapPluginProviderDelegate, MapControllable {
524524
@MapboxExperimental
525525
override fun removeWidget(widget: Widget) = mapController.removeWidget(widget)
526526

527+
/**
528+
* Schedules a thread service type reset on the render thread after a specified delay.
529+
* This method is called to ensure CPU affinity is properly set
530+
* when coming back from background, addressing timing issues where CPU affinity
531+
* might not be immediately available.
532+
* **Note:** This is a delicate API that should only be used when you experience
533+
* performance issues after returning from background. Improper usage may not
534+
* provide any benefits or could potentially affect rendering performance.
535+
*/
536+
@MapboxExperimental
537+
@MapboxDelicateApi
538+
fun scheduleThreadServiceTypeReset() {
539+
mapController.renderer.renderThread.scheduleThreadServiceTypeReset()
540+
}
541+
527542
/**
528543
* Add an instance of [RendererSetupErrorListener].
529544
*

maps-sdk/src/main/java/com/mapbox/maps/renderer/MapboxRenderThread.kt

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import android.opengl.EGL14
44
import android.opengl.EGLContext
55
import android.opengl.EGLSurface
66
import android.opengl.GLES20
7+
import android.os.Handler
8+
import android.os.Looper
79
import android.os.SystemClock
810
import android.os.Trace
911
import android.view.Choreographer
@@ -87,6 +89,13 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
8789
@OptIn(MapboxExperimental::class)
8890
internal var renderThreadStatsRecorder: RenderThreadStatsRecorder? = null
8991

92+
/**
93+
* Handler for posting tasks to the main thread.
94+
*/
95+
private val mainHandler by lazy {
96+
Handler(Looper.getMainLooper())
97+
}
98+
9099
/**
91100
* We track moment when native renderer is prepared.
92101
*/
@@ -708,21 +717,19 @@ internal class MapboxRenderThread : Choreographer.FrameCallback {
708717
}
709718

710719
/**
711-
* Schedules a resetThreadServiceType on the render thread after a specified delay.
712-
* This method is called on resume to ensure CPU affinity is properly set
720+
* Schedules a resetThreadServiceType on the main thread after a specified delay.
721+
* This method is called to ensure CPU affinity is properly set
713722
* when coming back from background, addressing timing issues where CPU affinity
714723
* might not be immediately available.
715724
*/
716725
@OptIn(MapboxExperimental::class)
717726
fun scheduleThreadServiceTypeReset() {
718727
logI(TAG, "Scheduling thread service type reset with delay")
719-
postNonRenderEvent(
720-
RenderEvent({
721-
logI(TAG, "Executing thread service type reset")
722-
mapboxRenderer.resetThreadServiceType()
723-
}, false),
724-
RESET_THREAD_SERVICE_TYPE_DELAY_MS
725-
)
728+
mainHandler.postDelayed({
729+
// log current thread name to help debug
730+
logI(TAG, "Executing thread service type reset from ${Thread.currentThread().name} thread")
731+
mapboxRenderer.resetThreadServiceType()
732+
}, RESET_THREAD_SERVICE_TYPE_DELAY_MS)
726733
}
727734

728735
@UiThread

maps-sdk/src/test/java/com/mapbox/maps/MapViewTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,4 +244,15 @@ class MapViewTest {
244244
mapView.removeRendererSetupErrorListener(listener)
245245
verify { mapController.removeRendererSetupErrorListener(listener) }
246246
}
247+
248+
@OptIn(MapboxExperimental::class, MapboxDelicateApi::class)
249+
@Test
250+
fun scheduleThreadServiceTypeReset() {
251+
val renderThread = mockk<com.mapbox.maps.renderer.MapboxRenderThread>(relaxUnitFun = true)
252+
val renderer = mockk<com.mapbox.maps.renderer.MapboxRenderer>(relaxed = true)
253+
every { mapController.renderer } returns renderer
254+
every { renderer.renderThread } returns renderThread
255+
mapView.scheduleThreadServiceTypeReset()
256+
verify { renderThread.scheduleThreadServiceTypeReset() }
257+
}
247258
}

maps-sdk/src/test/java/com/mapbox/maps/renderer/MapboxRenderThreadTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.robolectric.Shadows
2626
import org.robolectric.annotation.LooperMode
2727
import org.robolectric.shadows.ShadowChoreographer
2828
import org.robolectric.shadows.ShadowLog
29+
import org.robolectric.shadows.ShadowLooper
2930
import java.util.concurrent.CountDownLatch
3031
import java.util.concurrent.TimeUnit
3132
import java.util.concurrent.locks.Condition
@@ -1082,4 +1083,22 @@ class MapboxRenderThreadTest {
10821083
idleHandler()
10831084
every { logI(any(), any()) } answers { Log.i(firstArg(), secondArg()) }
10841085
}
1086+
1087+
@OptIn(com.mapbox.maps.MapboxExperimental::class)
1088+
@Test
1089+
fun scheduleThreadServiceTypeResetCallsRendererOnMainThread() {
1090+
initRenderThread()
1091+
1092+
// Verify resetThreadServiceType was not called initially
1093+
verify(exactly = 0) { mapboxRenderer.resetThreadServiceType() }
1094+
1095+
// Schedule the thread service type reset
1096+
mapboxRenderThread.scheduleThreadServiceTypeReset()
1097+
1098+
// Advance time by the delay amount (300ms) and process delayed main thread tasks
1099+
ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
1100+
1101+
// Verify that resetThreadServiceType was called exactly once on the renderer
1102+
verify(exactly = 1) { mapboxRenderer.resetThreadServiceType() }
1103+
}
10851104
}

0 commit comments

Comments
 (0)