java-topology/docs/tickets/rails-0002-callbacks-chain-index.md
russell@unturf.com 547a9f5738 ORM wave 2: 10 new defects — Active Record +3, Exposed +3, SeaORM +4 (167 sites, 64 ecosystems)
rails-0009: FilterAttributeHandler filter_parameters Array O(A×F) → Set (450×)
rails-0010: Encryption::AutoFilteredParameters two Array scans → Set (250×)
rails-0011: TimeZoneConversion skip_list Array O(M×C×S) → Set (20×)

exposed-0001: SchemaUtilityApi mapMissingColumnStatements O(N×M) → map (118×)
exposed-0002: IdentifierManagerApi isAKeyword O(K) linear → HashSet (144×)
exposed-0003: Table.clone consParams.map fresh List → hoisted HashSet (6×)

seaorm-0001: active_model establish_links leftover.any O(N²) → HashSet (501×)
seaorm-0002: rbac engine group_permissions .values().find() → HashMap by ID (502×)
seaorm-0003: schema builder sorted_tables Vec::contains → HashSet (500×)
seaorm-0004: TopologicalSort from_iter seen Vec O(N²) → BTreeSet (28×)

Unit tests: RailsTest 11/11, ExposedTest 3/3, SeaORMTest 4/4 PASS
Whitepaper: 157→167 sites, 62→64 ecosystems; §13.12 ORM Wave 2 added
2026-03-27 13:49:46 -04:00

918 B
Raw Permalink Blame History

rails-0002: Callbacks — O(C²) chain.index inside skip_callback filters loop

Severity: HIGH File: activesupport/lib/active_support/callbacks.rb Line: ~450 (CallbackChain#skip) Status: PATCHED

Description

skip_callback iterates filters.each across all descendants, and for each filter calls chain.index(callback) — an O(C) linear scan through the chain Array. With F filters and C callbacks per descendant class and D descendants, total complexity is O(D×F×C). In apps with deep inheritance hierarchies and many callbacks this becomes O(n³).

Root Cause

Array#index is a linear scan. The chain is rebuilt on every skip_callback call rather than maintaining a pre-built position map.

Fix

Pre-build a position_map = chain.each_with_index.to_h once before the filters loop so each lookup is O(1).

Speedup

~50x at D=50 descendants, F=100 filters, C=200 chain length