mapbox / mapbox/mapbox-maps-android

[Compose Extension] OOM crash in GeoJsonSourceState when data contains large feature list

Open Beginner friendly
#2,727 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug :beetle:
Dominant language
Kotlin
Stars
578
Forks
161
PR merge metrics
No merged PRs in 30d

Description

## Environment

- Android OS version: Any (lower-end devices with smaller growth limits hit it sooner)
- Devices affected: Any. Reproducible when the GeoJSON feature list is sufficiently large (crash observed at ~143 MB allocation attempt with a 201 MB growth limit)
- Maps SDK Version: Confirmed present on main as of the date of this report.

## Observed behavior and steps to reproduce

App crashes with OutOfMemoryError when a GeoJsonSourceState is backed by a GeoJSONData(List) containing a large number of features, due to an internal log call.

Steps:
1. Create a `GeoJsonSourceState` -> `sourceState.data = GeoJSONData(listOfFeatures)`.
2. Observe the app crash with the following OOM.

```
val sourceState = remember(sourceId) {
GeoJsonSourceState(sourceId = sourceId).apply {
...
}
}

LaunchedEffect(features) {
withContext(Dispatchers.Default) {
val finalFeatures = features.map { it.toMapboxFeature() }
sourceState.data = GeoJSONData(finalFeatures)
}
}

sourceState

```

Stack trace:
```
java.lang.OutOfMemoryError: Failed to allocate a 143240712 byte allocation with 25165824 free bytes and 49MB until OOM, target footprint 174756928, growth limit 201326592
at java.util.Arrays.copyOf(Arrays.java:3766)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:125)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:449)
at java.lang.StringBuilder.append(StringBuilder.java:137)
at java.lang.StringBuilder.append(StringBuilder.java:132)
at java.util.AbstractCollection.toString(AbstractCollection.java:473)
at com.mapbox.maps.extension.compose.style.sources.GeoJSONData.toString(PropertyTypes.kt:335)
at java.lang.String.valueOf(String.java:3657)
at java.lang.StringBuilder.append(StringBuilder.java:132)
at com.mapbox.maps.extension.compose.style.sources.SourceState$launchCollectGeoJsonData$1.invokeSuspend(SourceState.kt:230)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.java:34)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:124)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:586)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.execTask(CoroutineScheduler.kt:798)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:798)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:717)

Suppressed:
kotlinx.coroutines.internal.DiagnosticCoroutineContextException:
[CoroutineName(SourceStateMapboxMapScope_gps_source),
StandaloneCoroutine{Cancelling}@5888d8a,
Dispatchers.IO]

```

## Expected behavior

GeoJsonSourceState should handle large feature collections without crashing. Updating a source with thousands of GPS points is a standard use case. The SDK should not attempt to serialize the full feature list into a single String as a side-effect of a log call.

## Notes / preliminary analysis

SourceState.kt:230 — eager toString() in a log statement

```kotlin
// SourceState.kt ~line 228-233
private fun CoroutineScope.launchCollectGeoJsonData(mapboxMap: MapboxMap) =
launch(Dispatchers.IO) {
geoJSONDataChannel.consumeEach { data ->
logD(TAG, "setGeoJsonSourceData: $data") // <-- triggers toString() unconditionally
...
}
}
```

## Additional links and references

[SourceState.kt](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceState.kt)[ — ](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceState.kt)[launchCollectGeoJsonData](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceState.kt)
[(https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compos](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceState.kt)
[e/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceSta](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceState.kt)
[te.kt)](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceState.kt)

[PropertyTypes.kt](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/PropertyTypes.kt)[ — ](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/PropertyTypes.kt)[GeoJSONData.toString()](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/PropertyTypes.kt)
[(https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compos](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/PropertyTypes.kt)
[e/src/main/java/com/mapbox/maps/extension/compose/style/sources/PropertyT](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/PropertyTypes.kt)
[ypes.kt)](https://github.com/mapbox/mapbox-maps-android/blob/main/extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/PropertyTypes.kt)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with extension-compose/src/main/java/com/mapbox/maps/extension/compose/style/sources/SourceState.kt around launchCollectGeoJsonData at line 230, then inspect GeoJSONData.toString() in PropertyTypes.kt. Reproduce with a large GeoJSONData feature list and verify that updating the source no longer attempts to build the full feature-list string or crashes with OutOfMemoryError.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, kotlin
Domain
mobile, performance
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.