@@ -36,7 +36,6 @@ public final class Page implements Closeable {
3636 final int pageNum ;
3737 private long minSeqNum ; // TODO: see if we can make it final?
3838 private int elementCount ;
39- private long firstUnreadSeqNum ;
4039 private final Queue queue ;
4140 private final PageIO pageIO ;
4241 private boolean writable ;
@@ -45,41 +44,62 @@ public final class Page implements Closeable {
4544 // TODO: go steal LocalCheckpointService in feature/seq_no from ES
4645 // TODO: https://github.com/elastic/elasticsearch/blob/feature/seq_no/core/src/main/java/org/elasticsearch/index/seqno/LocalCheckpointService.java
4746 private final BitSet ackedSeqNums ;
47+ private final BitSet readSeqNums ;
4848 private Checkpoint lastCheckpoint ;
4949
50+ // Use {@link PageFactory}
5051 Page (int pageNum , Queue queue , long minSeqNum , int elementCount , long firstUnreadSeqNum , BitSet ackedSeqNums , @ NotNull PageIO pageIO , boolean writable ) {
5152 this .pageNum = pageNum ;
5253 this .queue = queue ;
5354
5455 this .minSeqNum = minSeqNum ;
5556 this .elementCount = elementCount ;
56- this .firstUnreadSeqNum = firstUnreadSeqNum ;
5757 this .ackedSeqNums = ackedSeqNums ;
58+ this .readSeqNums = readSeqNums (minSeqNum , firstUnreadSeqNum , elementCount );
5859 this .lastCheckpoint = new Checkpoint (0 , 0 , 0 , 0 , 0 );
5960 this .pageIO = pageIO ;
6061 this .writable = writable ;
6162
6263 assert this .pageIO != null : "invalid null pageIO" ;
6364 }
6465
66+ private static BitSet readSeqNums (final long minSeqNum , final long firstUnreadSeqNum , final int elementCount ) {
67+ final BitSet bitSet = new BitSet (elementCount );
68+ if (firstUnreadSeqNum > minSeqNum ) {
69+ bitSet .set (0 , (int ) (firstUnreadSeqNum - minSeqNum ));
70+ }
71+ return bitSet ;
72+ }
73+
6574 public String toString () {
66- return "pageNum=" + this .pageNum + ", minSeqNum=" + this .minSeqNum + ", elementCount=" + this .elementCount + ", firstUnreadSeqNum=" + this .firstUnreadSeqNum ;
75+ return "pageNum=" + this .pageNum +
76+ ", minSeqNum=" + this .minSeqNum +
77+ ", elementCount=" + this .elementCount +
78+ ", firstUnreadSeqNum=" + (this .minSeqNum + this .readSeqNums .nextClearBit (0 ));
6779 }
6880
6981 /**
70- * @param limit the maximum number of elements to read, actual number readcan be smaller
82+ * @param limit the maximum number of elements to read, actual number read can be smaller
7183 * @return {@link SequencedList} collection of serialized elements read
7284 * @throws IOException if an IO error occurs
7385 */
7486 public SequencedList <byte []> read (int limit ) throws IOException {
7587 // first make sure this page is activated, activating previously activated is harmless
7688 this .pageIO .activate ();
7789
78- SequencedList <byte []> serialized = this .pageIO .read (this .firstUnreadSeqNum , limit );
79- assert serialized .getSeqNums ().get (0 ) == this .firstUnreadSeqNum :
80- String .format ("firstUnreadSeqNum=%d != first result seqNum=%d" , this .firstUnreadSeqNum , serialized .getSeqNums ().get (0 ));
90+ // determine where to begin our read
91+ int firstUnreadOffset = this .readSeqNums .nextClearBit (0 );
92+ long firstUnreadSeqNum = this .minSeqNum + firstUnreadOffset ;
93+
94+ // determine how many contiguous events we can read without re-emitting any already-read events
95+ int nextReadOffset = this .readSeqNums .nextSetBit (firstUnreadOffset );
96+ int effectiveLimit = (nextReadOffset < 0 ) ? limit : Math .min (limit , nextReadOffset - firstUnreadOffset );
97+
98+ SequencedList <byte []> serialized = this .pageIO .read (firstUnreadSeqNum , effectiveLimit );
99+ assert serialized .getSeqNums ().get (0 ) == firstUnreadSeqNum :
100+ String .format ("firstUnreadSeqNum=%d != first result seqNum=%d" , firstUnreadSeqNum , serialized .getSeqNums ().get (0 ));
81101
82- this .firstUnreadSeqNum += serialized .getElements ().size ();
102+ this .readSeqNums . set ( firstUnreadOffset , firstUnreadOffset + serialized .getElements ().size () );
83103
84104 return serialized ;
85105 }
@@ -93,7 +113,6 @@ public void write(byte[] bytes, long seqNum, int checkpointMaxWrites) throws IOE
93113
94114 if (this .minSeqNum <= 0 ) {
95115 this .minSeqNum = seqNum ;
96- this .firstUnreadSeqNum = seqNum ;
97116 }
98117 this .elementCount ++;
99118
@@ -136,7 +155,11 @@ public boolean isFullyAcked() {
136155 }
137156
138157 public long unreadCount () {
139- return this .elementCount <= 0 ? 0 : Math .max (0 , (maxSeqNum () - this .firstUnreadSeqNum ) + 1 );
158+ if (this .elementCount <= 0 ) {
159+ return 0 ;
160+ }
161+
162+ return this .elementCount - this .readSeqNums .cardinality ();
140163 }
141164
142165 /**
@@ -187,6 +210,14 @@ public boolean ack(long firstSeqNum, int count, int checkpointMaxAcks) throws IO
187210 return done ;
188211 }
189212
213+ public boolean unread (long firstSeqNum , int count ) {
214+ final boolean wasFullyRead = isFullyRead ();
215+ int unreadChunkFirstOffset = Ints .checkedCast (Math .subtractExact (firstSeqNum , this .minSeqNum ));
216+ this .readSeqNums .clear (unreadChunkFirstOffset , unreadChunkFirstOffset +count );
217+
218+ return wasFullyRead ;
219+ }
220+
190221 public void checkpoint () throws IOException {
191222 if (this .writable ) {
192223 headPageCheckpoint ();
0 commit comments