@@ -147,6 +147,10 @@ class Cluster
147147 m_quality == QualityLevel::NEEDS_SPLIT_ACCEPTABLE;
148148 }
149149
150+ /* * Get the smallest number of transactions this Cluster is intended for. */
151+ virtual DepGraphIndex GetMinIntendedTxCount () const noexcept = 0;
152+ /* * Get the maximum number of transactions this Cluster supports. */
153+ virtual DepGraphIndex GetMaxTxCount () const noexcept = 0;
150154 /* * Total memory usage currently for this Cluster, including all its dynamic memory, plus Cluster
151155 * structure itself, and ClusterSet::m_clusters entry. */
152156 virtual size_t TotalMemoryUsage () const noexcept = 0;
@@ -246,11 +250,18 @@ class GenericClusterImpl final : public Cluster
246250 std::vector<DepGraphIndex> m_linearization;
247251
248252public:
253+ /* * The smallest number of transactions this Cluster implementation is intended for. */
254+ static constexpr DepGraphIndex MIN_INTENDED_TX_COUNT{1 };
255+ /* * The largest number of transactions this Cluster implementation supports. */
256+ static constexpr DepGraphIndex MAX_TX_COUNT{SetType::Size ()};
257+
249258 GenericClusterImpl () noexcept = delete ;
250259 /* * Construct an empty GenericClusterImpl. */
251260 explicit GenericClusterImpl (uint64_t sequence) noexcept ;
252261
253262 size_t TotalMemoryUsage () const noexcept final ;
263+ constexpr DepGraphIndex GetMinIntendedTxCount () const noexcept final { return MIN_INTENDED_TX_COUNT; }
264+ constexpr DepGraphIndex GetMaxTxCount () const noexcept final { return MAX_TX_COUNT; }
254265 DepGraphIndex GetDepGraphIndexRange () const noexcept final { return m_depgraph.PositionRange (); }
255266 LinearizationIndex GetTxCount () const noexcept final { return m_linearization.size (); }
256267 uint64_t GetTotalTxSize () const noexcept final ;
@@ -587,8 +598,11 @@ class TxGraphImpl final : public TxGraph
587598 * count. */
588599 std::unique_ptr<Cluster> CreateEmptyCluster (DepGraphIndex tx_count) noexcept
589600 {
590- (void )tx_count;
591- return CreateEmptyGenericCluster ();
601+ if (tx_count >= GenericClusterImpl::MIN_INTENDED_TX_COUNT && tx_count <= GenericClusterImpl::MAX_TX_COUNT) {
602+ return CreateEmptyGenericCluster ();
603+ }
604+ assert (false );
605+ return {};
592606 }
593607
594608 // Functions for handling Refs.
@@ -1135,10 +1149,12 @@ bool GenericClusterImpl::Split(TxGraphImpl& graph, int level) noexcept
11351149 auto component = m_depgraph.FindConnectedComponent (todo);
11361150 auto component_size = component.Count ();
11371151 auto split_quality = component_size <= 2 ? QualityLevel::OPTIMAL : new_quality;
1138- if (first && component == todo && SetType::Fill (component_size) == component) {
1152+ if (first && component == todo && SetType::Fill (component_size) == component && component_size >= MIN_INTENDED_TX_COUNT ) {
11391153 // The existing Cluster is an entire component, without holes. Leave it be, but update
11401154 // its quality. If there are holes, we continue, so that the Cluster is reconstructed
1141- // without holes, reducing memory usage.
1155+ // without holes, reducing memory usage. If the component's size is below the intended
1156+ // transaction count for this Cluster implementation, continue so that it can get
1157+ // converted.
11421158 Assume (todo == m_depgraph.Positions ());
11431159 graph.SetClusterQuality (level, m_quality, m_setindex, split_quality);
11441160 // If this made the quality ACCEPTABLE or OPTIMAL, we need to compute and cache its
@@ -1740,23 +1756,38 @@ void TxGraphImpl::Merge(std::span<Cluster*> to_merge, int level) noexcept
17401756 size_t max_size_pos{0 };
17411757 DepGraphIndex max_size = to_merge[max_size_pos]->GetTxCount ();
17421758 GetClusterSet (level).m_cluster_usage -= to_merge[max_size_pos]->TotalMemoryUsage ();
1759+ DepGraphIndex total_size = max_size;
17431760 for (size_t i = 1 ; i < to_merge.size (); ++i) {
17441761 GetClusterSet (level).m_cluster_usage -= to_merge[i]->TotalMemoryUsage ();
17451762 DepGraphIndex size = to_merge[i]->GetTxCount ();
1763+ total_size += size;
17461764 if (size > max_size) {
17471765 max_size_pos = i;
17481766 max_size = size;
17491767 }
17501768 }
17511769 if (max_size_pos != 0 ) std::swap (to_merge[0 ], to_merge[max_size_pos]);
17521770
1753- // Merge all further Clusters in the group into the first one, and delete them.
1754- for (size_t i = 1 ; i < to_merge.size (); ++i) {
1755- to_merge[0 ]->Merge (*this , level, *to_merge[i]);
1771+ size_t start_idx = 1 ;
1772+ Cluster* into_cluster = to_merge[0 ];
1773+ if (total_size > into_cluster->GetMaxTxCount ()) {
1774+ // The into_merge cluster is too small to fit all transactions being merged. Construct a
1775+ // a new Cluster using an implementation that matches the total size, and merge everything
1776+ // in there.
1777+ auto new_cluster = CreateEmptyCluster (total_size);
1778+ into_cluster = new_cluster.get ();
1779+ InsertCluster (level, std::move (new_cluster), QualityLevel::OPTIMAL);
1780+ start_idx = 0 ;
1781+ }
1782+
1783+ // Merge all further Clusters in the group into the result (first one, or new one), and delete
1784+ // them.
1785+ for (size_t i = start_idx; i < to_merge.size (); ++i) {
1786+ into_cluster->Merge (*this , level, *to_merge[i]);
17561787 DeleteCluster (*to_merge[i], level);
17571788 }
1758- to_merge[ 0 ] ->Compact ();
1759- GetClusterSet (level).m_cluster_usage += to_merge[ 0 ] ->TotalMemoryUsage ();
1789+ into_cluster ->Compact ();
1790+ GetClusterSet (level).m_cluster_usage += into_cluster ->TotalMemoryUsage ();
17601791}
17611792
17621793void TxGraphImpl::ApplyDependencies (int level) noexcept
@@ -2546,6 +2577,14 @@ void TxGraphImpl::SanityCheck() const
25462577 assert (cluster.IsOversized () || cluster.GetTotalTxSize () <= m_max_cluster_size);
25472578 // OVERSIZED clusters are singletons.
25482579 assert (!cluster.IsOversized () || cluster.GetTxCount () == 1 );
2580+ // Transaction counts cannot exceed the Cluster implementation's maximum
2581+ // supported transactions count.
2582+ assert (cluster.GetTxCount () <= cluster.GetMaxTxCount ());
2583+ // Unless a Split is yet to be applied, the number of transactions must not be
2584+ // below the Cluster implementation's intended transaction count.
2585+ if (!cluster.NeedsSplitting ()) {
2586+ assert (cluster.GetTxCount () >= cluster.GetMinIntendedTxCount ());
2587+ }
25492588
25502589 // Check the sequence number.
25512590 assert (cluster.m_sequence < m_next_sequence_counter);
0 commit comments