Repository navigation
FORM: make the read path thread-safe for multi-product form_source #1008
Description
Summary
One form_source creates a provider per configured product. All providers share the same form_reader → persistence_reader → storage_reader. Each provider is concurrency::serial, which only serializes a provider against itself. Providers for different products can call the same storage_reader concurrently.
ROOT container reads are already guarded by root_tbranch_read_mutex and root_rfield_read_mutex. Those locks do not protect storage_reader's own state: files_, read_containers_, and index_maps_ can be looked up and inserted from several threads at once. This causes undefined behavior and can corrupt a map or crash the read.
This race predates #1007 and is independent of the cell-index lookup fix in #992. The old get_index() mutated index_maps_ through operator[] on every call. The new implementation reduces mutation to initial index-map construction but does not make the read path thread-safe.
Required changes
- Audit shared mutable state across
form_reader,persistence_reader, andstorage_reader. - Protect lookup, initialization, and mutation of shared read-side state with a consistent synchronization strategy.
- Cover all externally callable storage-reader operations that access shared state, including
get_index,list_indices,prime, andread_container. - Ensure internal calls do not lock the same non-recursive mutex twice.
- Preserve the existing ROOT read locks and check lock ordering to avoid deadlocks.
Affected areas
form/form_source.cpp: provider construction and shared-reader ownership.form/persistence/persistence_reader.cppandform/persistence/persistence_reader.hpp: shared persistence-reader state.form/storage/storage_reader.cppandform/storage/storage_reader.hpp: shared file, container, and index caches.test/form: multi-product parallel read-back regression coverage.
Acceptance criteria
- A regression test reads multiple products through one
form_sourcewith-jgreater than 1. - The test checks each product's value against its expected cell and exercises initial cache construction as well as subsequent reads.
- Shared read-side state remains protected when different product providers execute concurrently.
- Existing cell-index lookup and read-back behavior remains correct.
- Run the regression under a race detector where supported, and record the results.
References
- Read-side counterpart of Make FORM's write path thread-safe independent of Phlex #840 (write path).
- Related: Enable ROOT thread safety during FORM initialization #989 (
ROOT::EnableThreadSafety). - Cell-index lookup issue: FORM: form_source serves another data cell's products for single-layer files written out of order #992.
- Originating PR: FORM: locate every cell through the index container #1007
- Review comment: FORM: locate every cell through the index container #1007 (comment)
Requested by @wwuoneway.
Activity