@Test void ordering() throws Exception { ManagedCursorContainer container = new ManagedCursorContainer(); ManagedCursor cursor1 = new MockManagedCursor(container, "test1", new PositionImpl(5, 5)); ManagedCursor cursor2 = new MockManagedCursor(container, "test2", new PositionImpl(5, 1)); ManagedCursor cursor3 = new MockManagedCursor(container, "test3", new PositionImpl(7, 1)); ManagedCursor cursor4 = new MockManagedCursor(container, "test4", new PositionImpl(6, 4)); ManagedCursor cursor5 = new MockManagedCursor(container, "test5", new PositionImpl(7, 0)); container.add(cursor1); container.add(cursor2); container.add(cursor3); container.add(cursor4); container.add(cursor5); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(5, 1)); container.removeCursor("test2"); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(5, 5)); container.removeCursor("test1"); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(6, 4)); container.removeCursor("test4"); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(7, 0)); container.removeCursor("test5"); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(7, 1)); container.removeCursor("test3"); assertTrue(container.isEmpty()); }
@Test void updatingCursorOutsideContainer() throws Exception { ManagedCursorContainer container = new ManagedCursorContainer(); ManagedCursor cursor1 = new MockManagedCursor(container, "test1", new PositionImpl(5, 5)); container.add(cursor1); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(5, 5)); MockManagedCursor cursor2 = new MockManagedCursor(container, "test2", new PositionImpl(2, 2)); container.add(cursor2); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(2, 2)); cursor2.position = new PositionImpl(8, 8); // Until we don't update the container, the ordering will not change assertEquals(container.getSlowestReaderPosition(), new PositionImpl(2, 2)); container.cursorUpdated(cursor2, cursor2.position); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(5, 5)); }
public void deactivateCursor(ManagedCursor cursor) { synchronized (activeCursors) { if (activeCursors.get(cursor.getName()) != null) { activeCursors.removeCursor(cursor.getName()); if (activeCursors.isEmpty()) { // cleanup cache if there is no active subscription entryCache.clear(); } else { // if removed subscription was the slowest subscription : update cursor and let it clear cache: // till new slowest-cursor's read-position discardEntriesFromCache((ManagedCursorImpl) activeCursors.getSlowestReader(), getPreviousPosition((PositionImpl) activeCursors.getSlowestReader().getReadPosition())); } } } }
/** * Push the item down towards the bottom of the tree (highest reading position). */ private void siftDown(final Item item) { while (true) { Item j = null; Item right = getRight(item); if (right != null && right.position.compareTo(item.position) < 0) { Item left = getLeft(item); if (left != null && left.position.compareTo(right.position) < 0) { j = left; } else { j = right; } } else { Item left = getLeft(item); if (left != null && left.position.compareTo(item.position) < 0) { j = left; } } if (j != null) { swap(item, j); } else { break; } } }
@Test void removingCursor() throws Exception { ManagedCursorContainer container = new ManagedCursorContainer(); ManagedCursor cursor1 = new MockManagedCursor(container, "test1", new PositionImpl(5, 5)); container.add(cursor1); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(5, 5)); assertEquals(container.get("test1"), cursor1); MockManagedCursor cursor2 = new MockManagedCursor(container, "test2", new PositionImpl(2, 2)); container.add(cursor2); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(2, 2)); assertEquals(container.get("test2"), cursor2); MockManagedCursor cursor3 = new MockManagedCursor(container, "test3", new PositionImpl(1, 1)); container.add(cursor3); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(1, 1)); assertEquals(container.get("test3"), cursor3); assertEquals(container, Lists.newArrayList(cursor1, cursor2, cursor3)); // Remove the cursor in the middle container.removeCursor("test2"); assertEquals(container, Lists.newArrayList(cursor1, cursor3)); assertEquals(container.get("test2"), null); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(1, 1)); container.removeCursor("test3"); assertEquals(container, Lists.newArrayList(cursor1)); assertEquals(container.getSlowestReaderPosition(), new PositionImpl(5, 5)); }
@Override public void operationComplete(Void result, Stat stat) { cursor.asyncDeleteCursorLedger(); cursors.removeCursor(consumerName); // Redo invalidation of entries in cache PositionImpl slowestConsumerPosition = cursors.getSlowestReaderPosition(); if (slowestConsumerPosition != null) { if (log.isDebugEnabled()) { log.debug("Doing cache invalidation up to {}", slowestConsumerPosition); } entryCache.invalidateEntries(slowestConsumerPosition); } else { entryCache.clear(); } trimConsumedLedgersInBackground(); log.info("[{}] [{}] Deleted cursor", name, consumerName); callback.deleteCursorComplete(ctx); }
/** * Signal that a cursor position has been updated and that the container must re-order the cursor list. * * @param cursor * @return a pair of positions, representing the previous slowest consumer and the new slowest consumer (after the * update). */ public Pair<PositionImpl, PositionImpl> cursorUpdated(ManagedCursor cursor, Position newPosition) { checkNotNull(cursor); long stamp = rwLock.writeLock(); try { Item item = cursors.get(cursor.getName()); if (item == null) { return null; } PositionImpl previousSlowestConsumer = heap.get(0).position; // When the cursor moves forward, we need to push it toward the // bottom of the tree and push it up if a reset was done item.position = (PositionImpl) newPosition; if (item.idx == 0 || getParent(item).position.compareTo(item.position) <= 0) { siftDown(item); } else { siftUp(item); } PositionImpl newSlowestConsumer = heap.get(0).position; return Pair.of(previousSlowestConsumer, newSlowestConsumer); } finally { rwLock.unlockWrite(stamp); } }
if (cursors.isEmpty()) { PositionImpl slowestReaderPosition = cursors.getSlowestReaderPosition(); if (slowestReaderPosition != null) { slowestReaderLedgerId = slowestReaderPosition.getLedgerId();
public boolean isCursorActive(ManagedCursor cursor) { return cursor.isDurable() && activeCursors.get(cursor.getName()) != null; }
void discardEntriesFromCache(ManagedCursorImpl cursor, PositionImpl newPosition) { Pair<PositionImpl, PositionImpl> pair = activeCursors.cursorUpdated(cursor, newPosition); if (pair != null) { entryCache.invalidateEntries(pair.getRight()); } }
/** * Tells whether the managed ledger has any active-cursor registered. * * @return true if at least a cursor exists */ public boolean hasActiveCursors() { return !activeCursors.isEmpty(); }
@Override public long getNumberOfActiveEntries() { long totalEntries = getNumberOfEntries(); PositionImpl pos = cursors.getSlowestReaderPosition(); if (pos == null) { // If there are no consumers, there are no active entries return 0; } else { // The slowest consumer will be in the first ledger in the list. We need to subtract the entries it has // already consumed in order to get the active entries count. return totalEntries - (pos.getEntryId() + 1); } }
@Override public void operationComplete() { log.info("[{}] Opened new cursor: {}", name, cursor); cursor.setActive(); // Update the ack position (ignoring entries that were written while the cursor was being created) cursor.initializeCursorPosition(initialPosition == InitialPosition.Latest ? getLastPositionAndCounter() : getFirstPositionAndCounter()); synchronized (this) { cursors.add(cursor); uninitializedCursors.remove(cursorName).complete(cursor); } callback.openCursorComplete(cursor, ctx); }
@Override public void setConfig(ManagedLedgerConfig config) { this.config = config; this.cursors.forEach(c -> c.setThrottleMarkDelete(config.getThrottleMarkDelete())); }
@Override public void operationComplete(Void result, Stat stat) { cursor.asyncDeleteCursorLedger(); cursors.removeCursor(consumerName); // Redo invalidation of entries in cache PositionImpl slowestConsumerPosition = cursors.getSlowestReaderPosition(); if (slowestConsumerPosition != null) { if (log.isDebugEnabled()) { log.debug("Doing cache invalidation up to {}", slowestConsumerPosition); } entryCache.invalidateEntries(slowestConsumerPosition); } else { entryCache.clear(); } trimConsumedLedgersInBackground(); log.info("[{}] [{}] Deleted cursor", name, consumerName); callback.deleteCursorComplete(ctx); }
/** * Push the item down towards the bottom of the tree (highest reading position) */ private void siftDown(final Item item) { while (true) { Item j = null; Item right = getRight(item); if (right != null && right.position.compareTo(item.position) < 0) { Item left = getLeft(item); if (left != null && left.position.compareTo(right.position) < 0) { j = left; } else { j = right; } } else { Item left = getLeft(item); if (left != null && left.position.compareTo(item.position) < 0) { j = left; } } if (j != null) { swap(item, j); } else { break; } } }