2828import io .questdb .client .std .Files ;
2929import io .questdb .client .std .FilesFacade ;
3030import io .questdb .client .std .MemoryTag ;
31- import io .questdb .client .std .ObjList ;
3231import io .questdb .client .std .Os ;
3332import io .questdb .client .std .QuietCloseable ;
3433import io .questdb .client .std .Unsafe ;
35- import io .questdb .client .std .str .Utf8s ;
3634import org .slf4j .Logger ;
3735import org .slf4j .LoggerFactory ;
3836
@@ -588,121 +586,6 @@ public long findLastFrameFsnWithoutPayloadFlag(int flagsOffset, int flagMask, in
588586 return best ;
589587 }
590588
591- /**
592- * Rebuilds, from the frames' own delta sections, the symbols a replay of
593- * {@code [minFsnInclusive .. maxFsnInclusive]} would register ABOVE {@code coverage},
594- * appending them to {@code out} in ascending id order. Returns the coverage the walk
595- * establishes (one past the highest id registered), or {@code -1} when a frame's delta
596- * starts ABOVE the coverage reached so far.
597- * <p>
598- * This is the recovery-time twin of {@code CursorWebSocketSendLoop.accumulateSentDict}:
599- * it visits the same frames, in the same FSN order, and extracts exactly the entries
600- * that method will add to the send loop's mirror as those frames go back on the wire.
601- * The producer's dictionary is seeded from it so the producer's delta baseline and the
602- * loop's mirror coverage stay in lockstep BY CONSTRUCTION -- which is what makes a slot
603- * whose {@code .symbol-dict} was torn (or lost entirely) recoverable, as long as the
604- * surviving frames define the ids themselves.
605- * <p>
606- * A {@code -1} return is a genuine GAP: the ids between the coverage and the frame's
607- * delta start were introduced by frames that were acked and trimmed away, so they lived
608- * ONLY in the lost dictionary and cannot be rebuilt from anything. That is the one case
609- * the data really must be resent, and it is exactly the condition the send loop's
610- * torn-dictionary guard ({@code deltaStart > sentDictCount}) would trip on at replay --
611- * so the two agree, rather than the producer failing on slots the drainer drains fine.
612- * <p>
613- * Same frame layout and FSN bound as {@link #maxSymbolDeltaEnd}.
614- */
615- public long collectReplaySymbolsAbove (
616- int headerMagic ,
617- int flagsOffset ,
618- int flagDeltaMask ,
619- int qwpHeaderSize ,
620- long minFsnInclusive ,
621- long maxFsnInclusive ,
622- long coverage ,
623- ObjList <String > out
624- ) {
625- long off = HEADER_SIZE ;
626- long frames = frameCount ;
627- for (long i = 0 ; i < frames ; i ++) {
628- long fsn = baseSeq + i ;
629- int payloadLen = Unsafe .getUnsafe ().getInt (mmapAddress + off + 4 );
630- long payload = mmapAddress + off + FRAME_HEADER_SIZE ;
631- if (fsn >= minFsnInclusive
632- && fsn <= maxFsnInclusive
633- && payloadLen >= qwpHeaderSize
634- && payloadLen > flagsOffset
635- && Unsafe .getUnsafe ().getInt (payload ) == headerMagic
636- && (Unsafe .getUnsafe ().getByte (payload + flagsOffset ) & flagDeltaMask ) != 0 ) {
637- long p = payload + qwpHeaderSize ;
638- long limit = payload + payloadLen ;
639- long deltaStart = 0L ;
640- int shift = 0 ;
641- while (p < limit ) {
642- byte b = Unsafe .getUnsafe ().getByte (p ++);
643- deltaStart |= (long ) (b & 0x7F ) << shift ;
644- if ((b & 0x80 ) == 0 ) {
645- break ;
646- }
647- shift += 7 ;
648- if (shift > 35 ) {
649- break ; // corrupt run; recovered frames are CRC-valid, so defensive only
650- }
651- }
652- long deltaCount = 0L ;
653- shift = 0 ;
654- while (p < limit ) {
655- byte b = Unsafe .getUnsafe ().getByte (p ++);
656- deltaCount |= (long ) (b & 0x7F ) << shift ;
657- if ((b & 0x80 ) == 0 ) {
658- break ;
659- }
660- shift += 7 ;
661- if (shift > 35 ) {
662- break ;
663- }
664- }
665- if (deltaStart > coverage ) {
666- return -1L ; // gap: ids [coverage, deltaStart) are unrecoverable
667- }
668- // Walk this frame's [len varint][utf8] entries. Ids below the coverage we
669- // already hold are skipped (a replayed frame legitimately overlaps the tip);
670- // ids at or above it extend the dictionary.
671- long id = deltaStart ;
672- for (long k = 0 ; k < deltaCount ; k ++, id ++) {
673- long len = 0L ;
674- shift = 0 ;
675- while (p < limit ) {
676- byte b = Unsafe .getUnsafe ().getByte (p ++);
677- len |= (long ) (b & 0x7F ) << shift ;
678- if ((b & 0x80 ) == 0 ) {
679- break ;
680- }
681- shift += 7 ;
682- if (shift > 35 ) {
683- break ;
684- }
685- }
686- if (p + len > limit ) {
687- // Malformed entry region. Do NOT guess: report it as unrecoverable
688- // so the caller fails clean rather than seeding a shifted dictionary.
689- return -1L ;
690- }
691- if (id >= coverage ) {
692- out .add (Utf8s .stringFromUtf8Bytes (p , p + len ));
693- }
694- p += len ;
695- }
696- long deltaEnd = deltaStart + deltaCount ;
697- if (deltaEnd > coverage ) {
698- coverage = deltaEnd ;
699- }
700- }
701- off += FRAME_HEADER_SIZE + payloadLen ;
702- }
703- return coverage ;
704- }
705-
706589 /**
707590 * Feeds every recovered frame in this segment to the engine's single-pass
708591 * recovery fold. Segments are visited oldest-first by {@link SegmentRing}.
@@ -717,146 +600,6 @@ void scanRecovery(RecoveredFrameAnalysis analysis) {
717600 }
718601 }
719602
720- /**
721- * Highest {@code deltaStart + deltaCount} (one past the highest symbol id) any
722- * delta-flagged frame in this segment carries, or {@code 0} only when NO
723- * delta-flagged frame is present. A frame with {@code deltaCount == 0} (a commit
724- * or a symbol-reusing frame -- the encoder always sets the delta flag) still
725- * contributes its {@code deltaStart}, i.e. the producer's baseline at encode
726- * time, NOT 0. That is deliberate: it anchors the torn-dict guard at that
727- * baseline so a dictionary torn below it is detected -- a conservative
728- * over-strand (it may over-reject a frame whose rows reference only surviving
729- * ids), never an under-strand that could silently shift the dense id map.
730- * Read-only walk over the recovered frames, used
731- * once at recovery to detect a persisted {@code .symbol-dict} torn (host crash,
732- * out-of-order page loss) below the ids the surviving frames still reference: if
733- * the max here reaches at or beyond the recovered dictionary size, a resuming
734- * producer -- seeded from the shorter dictionary -- would re-use ids the frames
735- * already define. Only frames at or below {@code maxFsnInclusive} count: frames
736- * above it are the aborted orphan-deferred tail, which {@code trySendOne} retires
737- * without ever transmitting, so their ids must not inflate the result (a resuming
738- * producer never reuses them on the wire). The frame layout mirrors
739- * {@link #findLastFrameFsnWithoutPayloadFlag}: the QWP message header
740- * ({@code qwpHeaderSize} bytes) is followed by the delta section
741- * {@code [deltaStart varint][deltaCount varint]...}.
742- *
743- * @param headerMagic the QWP message magic identifying a well-formed frame
744- * @param flagsOffset byte offset of the flags field within the QWP header
745- * @param flagDeltaMask the FLAG_DELTA_SYMBOL_DICT bit
746- * @param qwpHeaderSize the QWP message header size (delta section starts past it)
747- * @param maxFsnInclusive highest frame FSN to consider; frames above it are the
748- * retired orphan-deferred tail -- pass
749- * {@code recoveredCommitBoundaryFsn}
750- */
751- public long maxSymbolDeltaEnd (int headerMagic , int flagsOffset , int flagDeltaMask , int qwpHeaderSize , long maxFsnInclusive ) {
752- long maxEnd = 0L ;
753- long off = HEADER_SIZE ;
754- long frames = frameCount ;
755- for (long i = 0 ; i < frames ; i ++) {
756- long fsn = baseSeq + i ;
757- int payloadLen = Unsafe .getUnsafe ().getInt (mmapAddress + off + 4 );
758- long payload = mmapAddress + off + FRAME_HEADER_SIZE ;
759- // Skip the orphan-deferred tail (fsn > maxFsnInclusive): retired, never
760- // sent, so its ids must not count. off still advances below.
761- if (fsn <= maxFsnInclusive
762- && payloadLen >= qwpHeaderSize
763- && payloadLen > flagsOffset
764- && Unsafe .getUnsafe ().getInt (payload ) == headerMagic
765- && (Unsafe .getUnsafe ().getByte (payload + flagsOffset ) & flagDeltaMask ) != 0 ) {
766- long p = payload + qwpHeaderSize ;
767- long limit = payload + payloadLen ;
768- long deltaStart = 0L ;
769- int shift = 0 ;
770- while (p < limit ) {
771- byte b = Unsafe .getUnsafe ().getByte (p ++);
772- deltaStart |= (long ) (b & 0x7F ) << shift ;
773- if ((b & 0x80 ) == 0 ) {
774- break ;
775- }
776- shift += 7 ;
777- if (shift > 35 ) {
778- break ; // corrupt run; recovered frames are CRC-valid, so defensive only
779- }
780- }
781- long deltaCount = 0L ;
782- shift = 0 ;
783- while (p < limit ) {
784- byte b = Unsafe .getUnsafe ().getByte (p ++);
785- deltaCount |= (long ) (b & 0x7F ) << shift ;
786- if ((b & 0x80 ) == 0 ) {
787- break ;
788- }
789- shift += 7 ;
790- if (shift > 35 ) {
791- break ;
792- }
793- }
794- long end = deltaStart + deltaCount ;
795- if (end > maxEnd ) {
796- maxEnd = end ;
797- }
798- }
799- off += FRAME_HEADER_SIZE + payloadLen ;
800- }
801- return maxEnd ;
802- }
803-
804- /**
805- * Highest {@code deltaStart} any delta-flagged frame at or below
806- * {@code maxFsnInclusive} carries, or {@code 0} when NO delta-flagged frame is
807- * present. A result of {@code 0} means every surviving symbol-bearing frame is
808- * SELF-SUFFICIENT -- it re-registers its dictionary from id 0 (a full-dict
809- * frame), so the slot replays with no persisted dictionary at all; a result
810- * {@code > 0} means at least one frame is a non-self-sufficient delta whose ids
811- * depend on a prior frame's (or the persisted dictionary's) registrations.
812- * {@link CursorSendEngine} pairs this with {@link #maxSymbolDeltaEnd} at
813- * recovery: when the persisted {@code .symbol-dict} is a subset of the ids the
814- * frames reference BUT this returns {@code 0}, the slot was written in full-dict
815- * fallback (the dictionary never opened when writing) and recovers in full-dict
816- * mode instead of failing clean; a torn DELTA dictionary has a {@code
817- * deltaStart > 0} frame and still fails clean. Same read-only frame walk and
818- * layout as {@link #maxSymbolDeltaEnd}; only frames at or below {@code
819- * maxFsnInclusive} count (the retired orphan-deferred tail is excluded).
820- */
821- public long maxSymbolDeltaStart (int headerMagic , int flagsOffset , int flagDeltaMask , int qwpHeaderSize , long maxFsnInclusive ) {
822- long maxStart = 0L ;
823- long off = HEADER_SIZE ;
824- long frames = frameCount ;
825- for (long i = 0 ; i < frames ; i ++) {
826- long fsn = baseSeq + i ;
827- int payloadLen = Unsafe .getUnsafe ().getInt (mmapAddress + off + 4 );
828- long payload = mmapAddress + off + FRAME_HEADER_SIZE ;
829- // Skip the orphan-deferred tail (fsn > maxFsnInclusive): retired, never
830- // sent, so its baseline must not count. off still advances below.
831- if (fsn <= maxFsnInclusive
832- && payloadLen >= qwpHeaderSize
833- && payloadLen > flagsOffset
834- && Unsafe .getUnsafe ().getInt (payload ) == headerMagic
835- && (Unsafe .getUnsafe ().getByte (payload + flagsOffset ) & flagDeltaMask ) != 0 ) {
836- long p = payload + qwpHeaderSize ;
837- long limit = payload + payloadLen ;
838- long deltaStart = 0L ;
839- int shift = 0 ;
840- while (p < limit ) {
841- byte b = Unsafe .getUnsafe ().getByte (p ++);
842- deltaStart |= (long ) (b & 0x7F ) << shift ;
843- if ((b & 0x80 ) == 0 ) {
844- break ;
845- }
846- shift += 7 ;
847- if (shift > 35 ) {
848- break ; // corrupt run; recovered frames are CRC-valid, so defensive only
849- }
850- }
851- if (deltaStart > maxStart ) {
852- maxStart = deltaStart ;
853- }
854- }
855- off += FRAME_HEADER_SIZE + payloadLen ;
856- }
857- return maxStart ;
858- }
859-
860603 /**
861604 * Number of frames written since {@link #create} (or recovered by
862605 * {@link #openExisting}). Used by {@code SegmentRing} to compute
0 commit comments