Skip to content

Make keyed Compose HTML EAP rendering thread-safe - #5751

Merged
Clemens Koprolin (clemenskoprolin) merged 3 commits into
masterfrom
clemenskoprolin/compose-html-concurrency
Oct 9, 2026
Merged

Clemens Koprolin (clemenskoprolin) merged 3 commits into
masterfrom
clemenskoprolin/compose-html-concurrency

Conversation

@clemenskoprolin

Copy link
Copy Markdown
Collaborator

This PR makes keyed Compose HTML EAP string and streaming rendering thread-safe. Concurrent and nested calls, including calls with the same key, borrow separate renderers from a shared pool. Rendering storage can be reused across threads.

The JVM pool uses preallocated atomic slots to avoid allocating queue nodes when returning renderers. Attribute builders and logical output contents are cleared after each use. Pool retention is limited by the number of keys and idle renderers and oversized JVM output buffers are not retained.

The PR also:

  • Adds keyed overloads for renderHydratedDocument and both renderHydratedDocumentToStream variants.
  • Adds JMH benchmarks comparing fresh renderer creation, thread-local reuse and shared pooling.

Fixes CMP-10919

Testing

Run from the html directory:

./gradlew -Pcompose.html.eap.enabled=true \
  :html-core-eap:jvmTest \
  :html-core-eap:jsBrowserTest \
  :html-core-eap:wasmJsBrowserTest

Run the JVM benchmarks:

./gradlew -Pcompose.html.eap.enabled=true \
  :html-eap-benchmarks:run

Release Notes

N/A

@Measurement(iterations = 3, time = 1)
@Fork(value = 2, jvmArgsAppend = {"-Xms256m", "-Xmx256m"})
@Threads(4)
public class RendererPoolBenchmark {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this benchmark be written in Kotlin rather than Java?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I migrated the benchmark to Kotlin using kotlinx-benchmark.

return renderer.render(hydratable, requireHtmlDocumentRoot, chunkSink, chunkSize, prefix, content)
if (chunkSink != null) require(chunkSize > 0) { "chunkSize must be positive" }

val pool = key?.let(::getHtmlRendererPool)

@eymar Oleksandr Karpovich (eymar) Oct 9, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use a more straightforward syntax:

val pool = getHtmlRendererPool(key)
internal fun getHtmlRendererPool(key: String?): HtmlRendererPool? = if (key == null) null else renderers[key]

Simplify nullable renderer pool lookup, correct Kdocs
@clemenskoprolin
Clemens Koprolin (clemenskoprolin) merged commit 15c25e6 into master Oct 9, 2026
2 checks passed
@clemenskoprolin
Clemens Koprolin (clemenskoprolin) deleted the clemenskoprolin/compose-html-concurrency branch October 9, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants