Skip to content

[11.x.x] Parallelize per-sample test-pack generation in PipelineTestPackWriter - #388

Open
hugohills-regnosys wants to merge 2 commits into
finos:11.x.xfrom
REGnosys:hh/parallelize-testpack-sample-generation-11x
Open

hugohills-regnosys wants to merge 2 commits into
finos:11.x.xfrom
REGnosys:hh/parallelize-testpack-sample-generation-11x

Conversation

@hugohills-regnosys

@hugohills-regnosys hugohills-regnosys commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Backport of #11 onto the 11.x.x release line (which publishes as com.regnosys:rosetta-testing, the artifact actually consumed by current model repos, distinct from main's org.finos.rune-testing:rune-testing coordinates). Cherry-picked cleanly with no conflicts.

Summary

  • PipelineTestPackWriter.writeTestPackSamples() generated every sample in a test pack sequentially on a single thread, even though each sample is fully independent (own input file, own output file, own SampleModel). For models with large test-pack suites this loop dominates update-expectations runtime.
  • The inner per-sample loop is now driven by inputSamplesForTestPack.parallelStream(), with the per-sample body extracted into a new generateSample() method. Output is still re-sorted by sample ID afterward, so file layout and generated config JSON are unchanged.

Thread-safety review

  • RosettaTypeValidator, WorkflowPostProcessor and its constituent PostProcessSteps, and generated Rosetta functions all create mutable state fresh per call and hold only immutable config/factory fields — safe for concurrent invocation.
  • The one hazard: a single javax.xml.validation.Validator was reused across all samples in a test pack. PipelineFunctionRunnerProvider/Impl now take/hold a Schema and call schema.newValidator() per invocation. TransformTestExtension updated to match.
  • ValidationSummariser.addValidationReport(...) is now synchronized since it's a caller-supplied interface.

Test plan

  • mvn test — all 94 tests pass.
  • End-to-end validated against a real downstream model's update-expectations build: the run() phase went from 12.28 min to 4.43 min, and the regenerated expectation files were byte-for-byte identical to the pre-existing committed baseline.

hugohills-regnosys and others added 2 commits August 18, 2026 12:29
writeTestPackSamples() generated every sample in a test pack sequentially
on a single thread, even though each sample is fully independent (own
input file, own output file, own SampleModel). For a model with tens of
thousands of samples across many test packs, this loop dominates
update-expectations runtime (e.g. one BNPP pipeline: ~17,700 samples at
~39ms each = ~11.4 minutes, single-threaded).

Verified thread-safety of everything already shared across samples before
parallelizing: RosettaTypeValidator, WorkflowPostProcessor and its
constituent PostProcessSteps, and ReferenceResolverProcessStep all create
their mutable state fresh per call and only hold immutable config/factory
fields, so concurrent invocation is safe.

The one real hazard was the single javax.xml.validation.Validator built
once per test pack and reused across all its samples - Validator is
explicitly not thread-safe. PipelineFunctionRunnerProvider/Impl now take
a Schema instead and call schema.newValidator() per run() invocation
(cheap, and Schema is safe to share). TransformTestExtension updated to
match. Also synchronize the ValidationSummariser callback per sample,
since arbitrary implementations of that interface aren't guaranteed
thread-safe.

writeTestPackSamples' per-sample body is extracted into generateSample()
and driven by inputSamplesForTestPack.parallelStream(); output is
re-sorted by sample ID afterward as before, so file layout and generated
config are unchanged - only the generation order and wall-clock time.
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.

1 participant