@@ -756,52 +756,6 @@ static int32_t rx_cavl_compare_fragment_end(const void* const user, const udpard
756756 return 0 ; // clang-format on
757757}
758758
759- /// True if the fragment tree does not have a contiguous payload coverage in [left, right).
760- /// This function requires that the tree does not have fully-overlapped fragments; the implications are that
761- /// no two fragments may have the same offset, and that fragments ordered by offset also order by their ends.
762- /// The complexity is O(log n + k), where n is the number of fragments in the tree and k is the number of fragments
763- /// overlapping the specified range, assuming there are no fully-overlapped fragments.
764- static bool rx_fragment_tree_has_gap_in_range (udpard_tree_t * const root , const size_t left , const size_t right )
765- {
766- if (left >= right ) {
767- return false; // empty fragment; no gap by convention
768- }
769- udpard_fragment_t * frag = (udpard_fragment_t * )cavl2_predecessor (root , & left , & rx_cavl_compare_fragment_offset );
770- if (frag == NULL ) {
771- return true;
772- }
773- // offset+size can overflow on platforms with 16-bit size and requires special treatment there.
774- if ((frag -> offset + frag -> view .size ) <= left ) { // The predecessor ends before the left edge.
775- return true;
776- }
777- size_t covered = left ; // This is the O(k) part. The scan starting point search is O(log n).
778- while ((frag != NULL ) && (frag -> offset < right )) {
779- if (frag -> offset > covered ) {
780- return true;
781- }
782- covered = larger (covered , frag -> offset + frag -> view .size );
783- if (covered >= right ) {
784- return false; // Reached the end of the requested range without gaps.
785- }
786- frag = (udpard_fragment_t * )cavl2_next_greater (& frag -> index_offset );
787- }
788- return covered < right ;
789- }
790-
791- /// True if the specified fragment should be retained. Otherwise, it is redundant and should be discarded.
792- /// The complexity is O(log n + k), see rx_fragment_tree_has_gap_in_range() for details.
793- static bool rx_fragment_is_needed (udpard_tree_t * const root ,
794- const size_t frag_offset ,
795- const size_t frag_size ,
796- const size_t transfer_size ,
797- const size_t extent )
798- {
799- const size_t end = smaller3 (frag_offset + frag_size , transfer_size , extent );
800- // We need to special-case zero-size transfers because they are effectively just a single gap at offset zero.
801- return ((end == 0 ) && (frag_offset == 0 )) ||
802- ((frag_offset < extent ) && rx_fragment_tree_has_gap_in_range (root , frag_offset , end ));
803- }
804-
805759/// Finds the number of contiguous payload bytes received from offset zero after accepting a new fragment.
806760/// The transfer is considered fully received when covered_prefix >= min(extent, transfer_payload_size).
807761/// This should be invoked after the fragment tree accepted a new fragment at frag_offset with frag_size.
@@ -841,27 +795,51 @@ static rx_fragment_tree_update_result_t rx_fragment_tree_update(udpard_tree_t**
841795 const size_t extent ,
842796 size_t * const covered_prefix_io )
843797{
844- // The new fragment is needed in two cases:
845- // 1) It covers new ground in the [0, extent) range.
846- // 2) It overlaps smaller fragments that are already in the tree, which can be replaced.
847- // We do a quick lookup first to see if an existing fragment fully contains the new one;
848- // this simplifies the logic below an is very fast.
798+ // Do a quick lookup first to see if an existing fragment fully contains the new one or is equal,
799+ // in which case we can immediately discard the new fragment; this simplifies the logic below an is very fast.
800+ // This helps maintain a fundamental invariant of the fragment tree: no fully-contained fragments.
849801 const size_t left = frame .offset ;
850802 const size_t right = frame .offset + frame .payload .size ;
851803 udpard_fragment_t * frag = (udpard_fragment_t * )cavl2_predecessor (* root , & left , & rx_cavl_compare_fragment_offset );
852804 if ((frag != NULL ) && ((frag -> offset + frag -> view .size ) >= right )) {
853805 mem_free_payload (payload_deleter , frame .origin );
854- return rx_fragment_tree_not_done ; // New fragment is fully contained within an existing one.
806+ return rx_fragment_tree_not_done ; // New fragment is fully contained within an existing one, discard.
807+ }
808+
809+ // If the new fragment is not contained by another, we accept it even if it doesn't add new payload coverage.
810+ // This may seem counter-intuitive at first, and it is indeed very easy to query the tree to check if there
811+ // is a payload gap in a given range, but the reason we accept such fragments is that we speculate that
812+ // other large fragments may arrive in the future that will join with this one, allowing eviction of the smaller
813+ // ones, thus representing the full payload in a fewer number of fragments.
814+ // Consider the following scenario:
815+ // |--A--|--B--|--C--|--D--| <-- first fragment set
816+ // |---X---|---Y---|---Z---| <-- second fragment set
817+ // Suppose we already have A..D received. Arrival of either X or Z allows eviction of A/D immediately.
818+ // Arrival of Y does not allow an immediate eviction of any fragment, but if we had rejected it because it added
819+ // no new coverage, we would miss the opportunity to evict B/C when X or Z arrive later.
820+ // Therefore, we keep everything that comes our way as long as it is not smaller than what we already have.
821+ //
822+ // Ensure we can allocate the fragment header for the new frame before pruning the tree to avoid data loss.
823+ udpard_fragment_t * mew = mem_alloc (fragment_memory , sizeof (udpard_fragment_t ));
824+ if (mew == NULL ) {
825+ mem_free_payload (payload_deleter , frame .origin );
826+ return rx_fragment_tree_oom ; // Cannot allocate fragment header. Maybe we will succeed later.
855827 }
828+ mem_zero (sizeof (* mew ), mew );
829+ mew -> view .data = frame .payload .data ;
830+ mew -> view .size = frame .payload .size ;
831+ mew -> origin .data = frame .origin .data ;
832+ mew -> origin .size = frame .origin .size ;
833+ mew -> offset = frame .offset ;
834+ mew -> payload_deleter = payload_deleter ;
856835
857836 // The addition of a larger fragment that joins adjacent fragments together into a larger contiguous block may
858837 // render smaller fragments crossing its boundaries redundant.
859838 // To check for that, we create a new virtual fragment that represents the new fragment together with those
860839 // that join it on either end, if any, and then look for fragments contained within the virtual one.
861840 // Example:
862- // |--B--|
841+ // |--A-- |--B--|
863842 // |--X--|
864- // |--A--|
865843 // The addition of fragment A or B will render X redundant, even though it is not contained within either.
866844 // This algorithm will detect that and mark X for removal.
867845 //
@@ -884,39 +862,16 @@ static rx_fragment_tree_update_result_t rx_fragment_tree_update(udpard_tree_t**
884862 }
885863 UDPARD_ASSERT ((v_left <= left ) && (right <= v_right ));
886864
887- // Find the first victim. It has to be done early to decide if the new fragment is worth keeping at all.
888- frag = (udpard_fragment_t * )cavl2_lower_bound (* root , & v_left , & rx_cavl_compare_fragment_offset );
889- const bool need = ((frag != NULL ) && ((frag -> offset + frag -> view .size ) <= v_right )) ||
890- rx_fragment_is_needed (* root , frame .offset , frame .payload .size , transfer_payload_size , extent );
891- if (!need ) {
892- mem_free_payload (payload_deleter , frame .origin );
893- return rx_fragment_tree_not_done ; // Cannot make use of this fragment.
894- }
895-
896- // Ensure we can allocate the fragment header for the new frame before pruning the tree to avoid data loss.
897- udpard_fragment_t * mew = mem_alloc (fragment_memory , sizeof (udpard_fragment_t ));
898- if (mew == NULL ) {
899- mem_free_payload (payload_deleter , frame .origin );
900- return rx_fragment_tree_oom ; // Cannot allocate fragment header. Maybe we will succeed later.
901- }
902- mem_zero (sizeof (* mew ), mew );
903- mew -> view .data = frame .payload .data ;
904- mew -> view .size = frame .payload .size ;
905- mew -> origin .data = frame .origin .data ;
906- mew -> origin .size = frame .origin .size ;
907- mew -> offset = frame .offset ;
908- mew -> payload_deleter = payload_deleter ;
909-
910865 // Remove all redundant fragments before inserting the new one.
911- // No need to repeat tree lookup, we just step through the nodes using the next_greater lookup.
866+ // No need to repeat tree lookup at every iteration, we just step through the nodes using the next_greater lookup.
867+ frag = (udpard_fragment_t * )cavl2_lower_bound (* root , & v_left , & rx_cavl_compare_fragment_offset );
912868 while ((frag != NULL ) && (frag -> offset >= v_left ) && ((frag -> offset + frag -> view .size ) <= v_right )) {
913869 udpard_fragment_t * const next = (udpard_fragment_t * )cavl2_next_greater (& frag -> index_offset );
914870 cavl2_remove (root , & frag -> index_offset );
915871 mem_free_payload (frag -> payload_deleter , frag -> origin );
916872 mem_free (fragment_memory , sizeof (udpard_fragment_t ), frag );
917873 frag = next ;
918874 }
919-
920875 // Insert the new fragment.
921876 udpard_tree_t * const res = cavl2_find_or_insert (root , //
922877 & mew -> offset ,
@@ -925,8 +880,7 @@ static rx_fragment_tree_update_result_t rx_fragment_tree_update(udpard_tree_t**
925880 & cavl2_trivial_factory );
926881 UDPARD_ASSERT (res == & mew -> index_offset );
927882 (void )res ;
928-
929- // Update the covered prefix.
883+ // Update the covered prefix. This requires only a single full scan across all iterations!
930884 * covered_prefix_io = rx_fragment_tree_update_covered_prefix (* root , //
931885 * covered_prefix_io ,
932886 frame .offset ,
0 commit comments