• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wz2cool / local-queue / #69

05 Feb 2025 02:21PM UTC coverage: 91.605% (-0.2%) from 91.822%
#69

push

lyle
feat:try-catch 提交位点线程执行方法,防止异常后不再提交位点

2 of 4 new or added lines in 1 file covered. (50.0%)

742 of 810 relevant lines covered (91.6%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.68
/src/main/java/com/github/wz2cool/localqueue/impl/SimpleConsumer.java
1
package com.github.wz2cool.localqueue.impl;
2

3
import com.github.wz2cool.localqueue.IConsumer;
4
import com.github.wz2cool.localqueue.event.CloseListener;
5
import com.github.wz2cool.localqueue.helper.ChronicleQueueHelper;
6
import com.github.wz2cool.localqueue.model.config.SimpleConsumerConfig;
7
import com.github.wz2cool.localqueue.model.enums.ConsumeFromWhere;
8
import com.github.wz2cool.localqueue.model.message.InternalReadMessage;
9
import com.github.wz2cool.localqueue.model.message.QueueMessage;
10
import com.github.wz2cool.localqueue.model.page.PageInfo;
11
import com.github.wz2cool.localqueue.model.page.SortDirection;
12
import com.github.wz2cool.localqueue.model.page.UpDown;
13
import java.util.ArrayList;
14
import java.util.Arrays;
15
import java.util.Collections;
16
import java.util.List;
17
import java.util.Objects;
18
import java.util.Optional;
19
import java.util.Set;
20
import java.util.concurrent.CompletableFuture;
21
import java.util.concurrent.ConcurrentHashMap;
22
import java.util.concurrent.ConcurrentLinkedQueue;
23
import java.util.concurrent.ExecutorService;
24
import java.util.concurrent.Executors;
25
import java.util.concurrent.LinkedBlockingQueue;
26
import java.util.concurrent.ScheduledExecutorService;
27
import java.util.concurrent.TimeUnit;
28
import java.util.concurrent.atomic.AtomicBoolean;
29
import java.util.concurrent.atomic.AtomicInteger;
30
import java.util.concurrent.atomic.AtomicLong;
31
import net.openhft.chronicle.core.time.TimeProvider;
32
import net.openhft.chronicle.queue.ChronicleQueue;
33
import net.openhft.chronicle.queue.ExcerptTailer;
34
import net.openhft.chronicle.queue.RollCycle;
35
import net.openhft.chronicle.queue.TailerDirection;
36
import net.openhft.chronicle.queue.impl.single.SingleChronicleQueue;
37
import org.slf4j.Logger;
38
import org.slf4j.LoggerFactory;
39

40
/**
41
 * simple consumer
42
 *
43
 * @author frank
44
 */
45
public class SimpleConsumer implements IConsumer {
46

47
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
1✔
48
    private final RollCycle defaultRollCycle;
49
    private final TimeProvider timeProvider;
50
    private final Set<String> matchTags;
51
    private final SimpleConsumerConfig config;
52
    private final PositionStore positionStore;
53
    private final SingleChronicleQueue queue;
54
    // should only call by readCacheExecutor
55
    private final ExcerptTailer mainTailer;
56
    private final ExecutorService readCacheExecutor = Executors.newSingleThreadExecutor();
1✔
57
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
1✔
58
    private final LinkedBlockingQueue<QueueMessage> messageCache;
59
    private final ConcurrentLinkedQueue<CloseListener> closeListenerList = new ConcurrentLinkedQueue<>();
1✔
60
    private final AtomicLong ackedReadPosition = new AtomicLong(-1);
1✔
61
    private final AtomicBoolean isReadToCacheRunning = new AtomicBoolean(true);
1✔
62
    private final AtomicBoolean isClosing = new AtomicBoolean(false);
1✔
63
    private final AtomicBoolean isClosed = new AtomicBoolean(false);
1✔
64
    private final Object closeLocker = new Object();
1✔
65
    private final AtomicInteger positionVersion = new AtomicInteger(0);
1✔
66

67
    /**
68
     * constructor
69
     *
70
     * @param config the config of consumer
71
     */
72
    public SimpleConsumer(final SimpleConsumerConfig config) {
1✔
73
        this.config = config;
1✔
74
        this.matchTags = getMatchTags(config.getSelectTag());
1✔
75
        this.timeProvider = ChronicleQueueHelper.getTimeProvider(config.getTimeZone());
1✔
76
        this.messageCache = new LinkedBlockingQueue<>(config.getCacheSize());
1✔
77
        this.positionStore = new PositionStore(config.getPositionFile());
1✔
78
        this.defaultRollCycle = ChronicleQueueHelper.getRollCycle(config.getRollCycleType());
1✔
79
        this.queue = ChronicleQueue.singleBuilder(config.getDataDir())
1✔
80
                .timeProvider(timeProvider)
1✔
81
                .rollCycle(defaultRollCycle)
1✔
82
                .build();
1✔
83
        this.mainTailer = initMainTailer();
1✔
84
        startReadToCache();
1✔
85
        scheduler.scheduleAtFixedRate(this::flushPosition, 0, config.getFlushPositionInterval(), TimeUnit.MILLISECONDS);
1✔
86
    }
1✔
87

88

89
    private final List<QueueMessage> pendingMessages = Collections.synchronizedList(new ArrayList<>());
1✔
90

91
    @Override
92
    public synchronized QueueMessage take() throws InterruptedException {
93
        if (!pendingMessages.isEmpty()) {
1✔
94
            return pendingMessages.get(0);
×
95
        }
96
        QueueMessage message = this.messageCache.take();
1✔
97
        pendingMessages.add(message);
1✔
98
        return message;
1✔
99
    }
100

101
    @Override
102
    public synchronized List<QueueMessage> batchTake(int maxBatchSize) throws InterruptedException {
103
        if (!pendingMessages.isEmpty()) {
1✔
104
            return pendingMessages.subList(0, Math.min(maxBatchSize, pendingMessages.size()));
1✔
105
        }
106
        List<QueueMessage> result = new ArrayList<>(maxBatchSize);
1✔
107
        QueueMessage take = this.messageCache.take();
1✔
108
        result.add(take);
1✔
109
        this.messageCache.drainTo(result, maxBatchSize - 1);
1✔
110
        pendingMessages.addAll(result);
1✔
111
        return result;
1✔
112
    }
113

114
    @Override
115
    public synchronized Optional<QueueMessage> take(long timeout, TimeUnit unit) throws InterruptedException {
116
        if (!pendingMessages.isEmpty()) {
1✔
117
            return Optional.of(pendingMessages.get(0));
×
118
        }
119
        QueueMessage message = this.messageCache.poll(timeout, unit);
1✔
120
        if (Objects.nonNull(message)) {
1✔
121
            pendingMessages.add(message);
1✔
122
        }
123
        return Optional.ofNullable(message);
1✔
124
    }
125

126
    @Override
127
    public synchronized List<QueueMessage> batchTake(int maxBatchSize, long timeout, TimeUnit unit) throws InterruptedException {
128
        if (!pendingMessages.isEmpty()) {
1✔
129
            return pendingMessages.subList(0, Math.min(maxBatchSize, pendingMessages.size()));
×
130
        }
131
        List<QueueMessage> result = new ArrayList<>(maxBatchSize);
1✔
132
        QueueMessage poll = this.messageCache.poll(timeout, unit);
1✔
133
        if (Objects.nonNull(poll)) {
1✔
134
            result.add(poll);
1✔
135
            this.messageCache.drainTo(result, maxBatchSize - 1);
1✔
136
            pendingMessages.addAll(result);
1✔
137
        }
138
        return result;
1✔
139
    }
140

141
    @Override
142
    public synchronized Optional<QueueMessage> poll() {
143
        if (!pendingMessages.isEmpty()) {
1✔
144
            return Optional.of(pendingMessages.get(0));
×
145
        }
146
        QueueMessage message = this.messageCache.poll();
1✔
147
        if (Objects.nonNull(message)) {
1✔
148
            pendingMessages.add(message);
1✔
149
        }
150
        return Optional.ofNullable(message);
1✔
151
    }
152

153
    @Override
154
    public synchronized List<QueueMessage> batchPoll(int maxBatchSize) {
155
        if (!pendingMessages.isEmpty()) {
1✔
156
            return pendingMessages.subList(0, Math.min(maxBatchSize, pendingMessages.size()));
×
157
        }
158
        List<QueueMessage> result = new ArrayList<>(maxBatchSize);
1✔
159
        this.messageCache.drainTo(result, maxBatchSize);
1✔
160
        pendingMessages.addAll(result);
1✔
161
        return result;
1✔
162
    }
163

164
    @Override
165
    public synchronized void ack(final QueueMessage message) {
166
        if (Objects.isNull(message)) {
1✔
167
            return;
1✔
168
        }
169

170
        if (message.getPositionVersion() != positionVersion.get()) {
1✔
171
            return;
×
172
        }
173
        ackedReadPosition.set(message.getPosition());
1✔
174
        pendingMessages.remove(message);
1✔
175
    }
1✔
176

177
    @Override
178
    public synchronized void ack(final List<QueueMessage> messages) {
179
        if (Objects.isNull(messages) || messages.isEmpty()) {
1✔
180
            return;
1✔
181
        }
182
        QueueMessage lastOne = messages.get(messages.size() - 1);
1✔
183
        if (lastOne.getPositionVersion() != positionVersion.get()) {
1✔
184
            return;
×
185
        }
186
        ackedReadPosition.set(lastOne.getPosition());
1✔
187
        pendingMessages.removeAll(messages);
1✔
188
    }
1✔
189

190
    @Override
191
    public boolean moveToPosition(final long position) {
192
        logDebug("[moveToPosition] start");
1✔
193
        stopReadToCache();
1✔
194
        try {
195
            return moveToPositionInternal(position);
1✔
196
        } finally {
197
            startReadToCache();
1✔
198
            logDebug("[moveToPosition] end");
1✔
199
        }
×
200
    }
201

202
    @Override
203
    public boolean moveToTimestamp(final long timestamp) {
204
        logDebug("[moveToTimestamp] start, timestamp: {}", timestamp);
1✔
205
        stopReadToCache();
1✔
206
        try {
207
            Optional<Long> positionOptional = findPosition(timestamp);
1✔
208
            if (!positionOptional.isPresent()) {
1✔
209
                return false;
1✔
210
            }
211
            Long position = positionOptional.get();
1✔
212
            return moveToPositionInternal(position);
1✔
213
        } finally {
214
            startReadToCache();
1✔
215
            logDebug("[moveToTimestamp] end");
1✔
216
        }
×
217
    }
218

219
    @Override
220
    public Optional<QueueMessage> get(final long position) {
221
        if (position < 0) {
1✔
222
            return Optional.empty();
×
223
        }
224
        try (ExcerptTailer tailer = queue.createTailer()) {
1✔
225
            tailer.moveToIndex(position);
1✔
226
            InternalReadMessage internalReadMessage = new InternalReadMessage();
1✔
227
            boolean readResult = tailer.readBytes(internalReadMessage);
1✔
228
            if (readResult) {
1✔
229
                return Optional.of(toQueueMessage(internalReadMessage, position));
1✔
230
            } else {
231
                return Optional.empty();
×
232
            }
233
        }
1✔
234
    }
235

236
    @Override
237
    public Optional<QueueMessage> get(final String messageKey, long searchTimestampStart, long searchTimestampEnd) {
238
        if (messageKey == null || messageKey.isEmpty()) {
1✔
239
            return Optional.empty();
1✔
240
        }
241
        // reuse this message
242
        InternalReadMessage internalReadMessage = new InternalReadMessage();
1✔
243
        try (ExcerptTailer tailer = queue.createTailer()) {
1✔
244
            moveToNearByTimestamp(tailer, searchTimestampStart);
1✔
245
            while (true) {
246
                // for performance, ignore read content.
247
                boolean readResult = tailer.readBytes(internalReadMessage);
1✔
248
                if (!readResult) {
1✔
249
                    return Optional.empty();
1✔
250
                }
251
                if (internalReadMessage.getWriteTime() < searchTimestampStart) {
1✔
252
                    continue;
1✔
253
                }
254
                if (internalReadMessage.getWriteTime() > searchTimestampEnd) {
1✔
255
                    return Optional.empty();
×
256
                }
257
                boolean moveToResult = tailer.moveToIndex(tailer.lastReadIndex());
1✔
258
                if (!moveToResult) {
1✔
259
                    return Optional.empty();
×
260
                }
261
                readResult = tailer.readBytes(internalReadMessage);
1✔
262
                if (!readResult) {
1✔
263
                    return Optional.empty();
×
264
                }
265
                QueueMessage queueMessage = toQueueMessage(internalReadMessage, tailer.lastReadIndex());
1✔
266
                if (Objects.equals(messageKey, queueMessage.getMessageKey())) {
1✔
267
                    return Optional.of(queueMessage);
1✔
268
                }
269
            }
×
270
        }
1✔
271
    }
272

273
    private Set<String> getMatchTags(String selectTag) {
274
        logDebug("[getMatchTags] start, selectTag: {}", selectTag);
1✔
275
        ConcurrentHashMap.KeySetView<String, Boolean> mySet = ConcurrentHashMap.newKeySet();
1✔
276
        if (selectTag == null || selectTag.isEmpty()) {
1✔
277
            return mySet;
×
278
        }
279

280
        String[] tags = selectTag.split("\\|\\|");
1✔
281
        mySet.addAll(Arrays.asList(tags));
1✔
282
        return mySet;
1✔
283
    }
284

285
    private QueueMessage toQueueMessage(final InternalReadMessage internalReadMessage, final long position) {
286
        return new QueueMessage(
1✔
287
                internalReadMessage.getTag(),
1✔
288
                internalReadMessage.getMessageKey(),
1✔
289
                positionVersion.get(),
1✔
290
                position,
291
                internalReadMessage.getContent(),
1✔
292
                internalReadMessage.getWriteTime());
1✔
293
    }
294

295
    private boolean moveToPositionInternal(final long position) {
296
        return CompletableFuture.supplyAsync(() -> {
1✔
297
            synchronized (closeLocker) {
1✔
298
                try {
299
                    if (isClosing.get()) {
1✔
300
                        logDebug("[moveToPositionInternal] consumer is closing");
×
301
                        return false;
×
302
                    }
303
                    logDebug("[moveToPositionInternal] start, position: {}", position);
1✔
304
                    boolean moveToResult = mainTailer.moveToIndex(position);
1✔
305
                    if (moveToResult) {
1✔
306
                        positionVersion.incrementAndGet();
1✔
307
                        messageCache.clear();
1✔
308
                        ackedReadPosition.set(position);
1✔
309
                    }
310
                    return moveToResult;
1✔
311
                } finally {
312
                    logDebug("[moveToPositionInternal] end");
1✔
313
                }
×
314
            }
×
315
        }, this.readCacheExecutor).join();
1✔
316
    }
317

318

319
    @Override
320
    public Optional<Long> findPosition(final long timestamp) {
321
        logDebug("[findPosition] start, timestamp: {}", timestamp);
1✔
322
        try (ExcerptTailer tailer = queue.createTailer()) {
1✔
323
            moveToNearByTimestamp(tailer, timestamp);
1✔
324
            // reuse this message.
325
            InternalReadMessage internalReadMessage = new InternalReadMessage(true);
1✔
326
            while (true) {
327
                boolean resultResult = tailer.readBytes(internalReadMessage);
1✔
328
                if (resultResult) {
1✔
329
                    if (internalReadMessage.getWriteTime() >= timestamp) {
1✔
330
                        return Optional.of(tailer.lastReadIndex());
1✔
331
                    }
332
                } else {
333
                    return Optional.empty();
1✔
334
                }
335
            }
1✔
336
        } finally {
1✔
337
            logDebug("[findPosition] end");
1✔
338
        }
×
339
    }
340

341
    public long getAckedReadPosition() {
342
        return ackedReadPosition.get();
1✔
343
    }
344

345
    @Override
346
    public boolean isClosed() {
347
        return isClosed.get();
1✔
348
    }
349

350
    private void stopReadToCache() {
351
        isReadToCacheRunning.set(false);
1✔
352
    }
1✔
353

354
    private void startReadToCache() {
355
        this.isReadToCacheRunning.set(true);
1✔
356
        readCacheExecutor.execute(this::readToCache);
1✔
357
    }
1✔
358

359
    private void readToCache() {
360
        try {
361
            logDebug("[readToCache] start");
1✔
362
            long pullInterval = config.getPullInterval();
1✔
363
            long fillCacheInterval = config.getFillCacheInterval();
1✔
364
            // reuse this message.
365
            InternalReadMessage internalReadMessage = new InternalReadMessage(this.matchTags);
1✔
366
            while (isReadToCacheRunning.get()) {
1✔
367
                synchronized (closeLocker) {
1✔
368
                    try {
369
                        if (isClosing.get()) {
1✔
370
                            logDebug("[readToCache] consumer is closing");
1✔
371
                            return;
1✔
372
                        }
373

374
                        boolean readResult = mainTailer.readBytes(internalReadMessage);
1✔
375
                        if (!readResult) {
1✔
376
                            TimeUnit.MILLISECONDS.sleep(pullInterval);
1✔
377
                            continue;
1✔
378
                        }
379
                        String messageTag = internalReadMessage.getTag() == null ? "*" : internalReadMessage.getTag();
1✔
380
                        if (matchTags.contains("*") || matchTags.contains(messageTag)) {
1✔
381
                            long lastedReadIndex = mainTailer.lastReadIndex();
1✔
382
                            QueueMessage queueMessage = toQueueMessage(internalReadMessage, lastedReadIndex);
1✔
383
                            boolean offerResult = this.messageCache.offer(queueMessage, fillCacheInterval, TimeUnit.MILLISECONDS);
1✔
384
                            if (!offerResult) {
1✔
385
                                // if offer failed, move to last read position
386
                                mainTailer.moveToIndex(lastedReadIndex);
1✔
387
                            }
388
                        }
389
                    } catch (InterruptedException e) {
×
390
                        Thread.currentThread().interrupt();
×
391
                    }
1✔
392
                }
1✔
393
            }
394
        } finally {
395
            logDebug("[readToCache] end");
1✔
396
        }
1✔
397
    }
1✔
398

399
    private ExcerptTailer initMainTailer() {
400
        return CompletableFuture.supplyAsync(this::initMainTailerInternal, this.readCacheExecutor).join();
1✔
401
    }
402

403
    private ExcerptTailer initMainTailerInternal() {
404
        try {
405
            logDebug("[initExcerptTailerInternal] start");
1✔
406
            ExcerptTailer tailer = queue.createTailer();
1✔
407
            Optional<Long> lastPositionOptional = getLastPosition();
1✔
408
            if (lastPositionOptional.isPresent()) {
1✔
409
                Long position = lastPositionOptional.get();
1✔
410
                long beginPosition = position + 1;
1✔
411
                tailer.moveToIndex(beginPosition);
1✔
412
                logDebug("[initExcerptTailerInternal] find last position and move to position: {}", beginPosition);
1✔
413
            } else {
1✔
414
                ConsumeFromWhere consumeFromWhere = this.config.getConsumeFromWhere();
1✔
415
                if (consumeFromWhere == ConsumeFromWhere.LAST) {
1✔
416
                    tailer.toEnd();
1✔
417
                    logDebug("[initExcerptTailerInternal] move to end");
1✔
418
                } else if (consumeFromWhere == ConsumeFromWhere.FIRST) {
1✔
419
                    tailer.toStart();
1✔
420
                    logDebug("[initExcerptTailerInternal] move to start");
1✔
421
                }
422
            }
423
            return tailer;
1✔
424
        } finally {
425
            logDebug("[initExcerptTailer] end");
1✔
426
        }
×
427

428
    }
429

430
    /// region position
431

432
    private void flushPosition() {
433
        try {
434
            if (ackedReadPosition.get() != -1) {
1✔
435
                setLastPosition(this.ackedReadPosition.get());
1✔
436
            }
NEW
437
        } catch (Exception e) {
×
NEW
438
            logger.error("flushPosition Exception", e);
×
439
        }
1✔
440
    }
1✔
441

442
    private Optional<Long> getLastPosition() {
443
        return positionStore.get(config.getConsumerId());
1✔
444
    }
445

446
    private void setLastPosition(long position) {
447
        positionStore.put(config.getConsumerId(), position);
1✔
448
    }
1✔
449

450
    /// endregion
451

452
    @SuppressWarnings("Duplicates")
453
    @Override
454
    public void close() {
455
        synchronized (closeLocker) {
1✔
456
            try {
457
                logDebug("[close] start");
1✔
458
                if (isClosing.get()) {
1✔
459
                    logDebug("[close] is closing");
1✔
460
                    return;
1✔
461
                }
462
                isClosing.set(true);
1✔
463
                stopReadToCache();
1✔
464
                if (!positionStore.isClosed()) {
1✔
465
                    positionStore.close();
1✔
466
                }
467
                scheduler.shutdown();
1✔
468
                readCacheExecutor.shutdown();
1✔
469
                try {
470
                    if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
1✔
471
                        scheduler.shutdownNow();
×
472
                    }
473
                    if (!readCacheExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
1✔
474
                        readCacheExecutor.shutdownNow();
1✔
475
                    }
476
                } catch (InterruptedException e) {
×
477
                    scheduler.shutdownNow();
×
478
                    readCacheExecutor.shutdownNow();
×
479
                    Thread.currentThread().interrupt();
×
480
                }
1✔
481
                if (!queue.isClosed()) {
1✔
482
                    queue.close();
1✔
483
                }
484

485
                for (CloseListener closeListener : closeListenerList) {
1✔
486
                    closeListener.onClose();
1✔
487
                }
1✔
488
                isClosed.set(true);
1✔
489
            } finally {
490
                logDebug("[close] end");
1✔
491
            }
1✔
492
        }
1✔
493
    }
1✔
494

495
    private void moveToNearByTimestamp(ExcerptTailer tailer, long timestamp) {
496
        int expectedCycle = ChronicleQueueHelper.cycle(defaultRollCycle, timeProvider, timestamp);
1✔
497
        int currentCycle = tailer.cycle();
1✔
498
        if (currentCycle != expectedCycle) {
1✔
499
            boolean moveToCycleResult = tailer.moveToCycle(expectedCycle);
1✔
500
            logDebug("[moveToNearByTimestamp] moveToCycleResult: {}", moveToCycleResult);
1✔
501
        }
502
    }
1✔
503

504
    @Override
505
    public void addCloseListener(CloseListener listener) {
506
        closeListenerList.add(listener);
1✔
507
    }
1✔
508

509
    // region page
510

511
    @Override
512
    public PageInfo<QueueMessage> getPage(SortDirection sortDirection, int pageSize) {
513
        return getPage(-1, sortDirection, pageSize);
1✔
514
    }
515

516
    @SuppressWarnings("Duplicates")
517
    @Override
518
    public PageInfo<QueueMessage> getPage(long moveToPosition, SortDirection sortDirection, int pageSize) {
519
        try (ExcerptTailer tailer = queue.createTailer()) {
1✔
520
            if (moveToPosition != -1) {
1✔
521
                tailer.moveToIndex(moveToPosition);
1✔
522
            }
523
            if (sortDirection == SortDirection.DESC) {
1✔
524
                tailer.toEnd();
1✔
525
                tailer.direction(TailerDirection.BACKWARD);
1✔
526
            }
527
            List<QueueMessage> data = new ArrayList<>();
1✔
528
            long start = -1;
1✔
529
            long end = -1;
1✔
530
            // reuse this message.
531
            InternalReadMessage internalReadMessage = new InternalReadMessage();
1✔
532
            for (int i = 0; i < pageSize; i++) {
1✔
533
                boolean readResult = tailer.readBytes(internalReadMessage);
1✔
534
                if (!readResult) {
1✔
535
                    break;
1✔
536
                }
537
                QueueMessage queueMessage = toQueueMessage(internalReadMessage, tailer.lastReadIndex());
1✔
538
                data.add(queueMessage);
1✔
539
                if (i == 0) {
1✔
540
                    start = tailer.lastReadIndex();
1✔
541
                }
542
                end = tailer.lastReadIndex();
1✔
543
            }
544
            return new PageInfo<>(start, end, data, sortDirection, pageSize);
1✔
545
        }
1✔
546
    }
547

548
    @SuppressWarnings("Duplicates")
549
    @Override
550
    public PageInfo<QueueMessage> getPage(PageInfo<QueueMessage> prevPageInfo, UpDown upDown) {
551
        SortDirection sortDirection = prevPageInfo.getSortDirection();
1✔
552
        int pageSize = prevPageInfo.getPageSize();
1✔
553
        long start = prevPageInfo.getStart();
1✔
554
        long end = prevPageInfo.getEnd();
1✔
555
        try (ExcerptTailer tailer = queue.createTailer()) {
1✔
556
            TailerDirection tailerDirection = getTailerDirection(sortDirection, upDown);
1✔
557
            tailer.direction(tailerDirection);
1✔
558
            if (sortDirection == SortDirection.DESC) {
1✔
559
                if (upDown == UpDown.DOWN) {
1✔
560
                    tailer.moveToIndex(end - 1);
1✔
561
                } else {
562
                    tailer.moveToIndex(start + 1);
1✔
563
                }
564
            } else {
565
                if (upDown == UpDown.DOWN) {
1✔
566
                    tailer.moveToIndex(end + 1);
1✔
567
                } else {
568
                    tailer.moveToIndex(start - 1);
1✔
569
                }
570
            }
571
            List<QueueMessage> data = new ArrayList<>();
1✔
572
            // reuse this message.
573
            InternalReadMessage internalReadMessage = new InternalReadMessage();
1✔
574
            for (int i = 0; i < pageSize; i++) {
1✔
575
                boolean readResult = tailer.readBytes(internalReadMessage);
1✔
576
                if (!readResult) {
1✔
577
                    break;
×
578
                }
579
                QueueMessage queueMessage = toQueueMessage(internalReadMessage, tailer.lastReadIndex());
1✔
580
                data.add(queueMessage);
1✔
581
                if (i == 0) {
1✔
582
                    start = tailer.lastReadIndex();
1✔
583
                }
584
                end = tailer.lastReadIndex();
1✔
585
            }
586
            if (upDown == UpDown.UP) {
1✔
587
                Collections.reverse(data);
1✔
588
            }
589
            return new PageInfo<>(start, end, data, sortDirection, pageSize);
1✔
590
        }
1✔
591
    }
592

593
    private TailerDirection getTailerDirection(SortDirection sortDirection, UpDown upDown) {
594
        if (sortDirection == SortDirection.DESC && upDown == UpDown.DOWN) {
1✔
595
            return TailerDirection.BACKWARD;
1✔
596
        }
597
        if (sortDirection == SortDirection.DESC && upDown == UpDown.UP) {
1✔
598
            return TailerDirection.FORWARD;
1✔
599
        }
600
        if (sortDirection == SortDirection.ASC && upDown == UpDown.DOWN) {
1✔
601
            return TailerDirection.FORWARD;
1✔
602
        }
603
        if (sortDirection == SortDirection.ASC && upDown == UpDown.UP) {
1✔
604
            return TailerDirection.BACKWARD;
1✔
605
        }
606
        return TailerDirection.FORWARD;
×
607
    }
608

609
    // endregion
610

611
    // region logger
612

613
    private void logDebug(String format) {
614
        if (logger.isDebugEnabled()) {
1✔
615
            logger.debug(format);
×
616
        }
617
    }
1✔
618

619
    private void logDebug(String format, Object arg) {
620
        if (logger.isDebugEnabled()) {
1✔
621
            logger.debug(format, arg);
×
622
        }
623
    }
1✔
624

625
    // endregion
626
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc