@Override public void extendClaim(String processorName, int segment) throws UnableToClaimTokenException { EntityManager entityManager = entityManagerProvider.getEntityManager(); int updates = entityManager.createQuery("UPDATE TokenEntry te SET te.timestamp = :timestamp " + "WHERE te.processorName = :processorName " + "AND te.segment = :segment " + "AND te.owner = :owner") .setParameter("processorName", processorName) .setParameter("segment", segment) .setParameter("owner", nodeId) .setParameter("timestamp", formatInstant(TokenEntry.clock.instant())) .executeUpdate(); if (updates == 0) { throw new UnableToClaimTokenException("Unable to extend the claim on token for processor '" + processorName + "[" + segment + "]'. It is either claimed " + "by another process, or there is no such token."); } }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { EntityManager entityManager = entityManagerProvider.getEntityManager(); if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException("Could not initialize segments. Some segments were already present."); } for (int segment = 0; segment < segmentCount; segment++) { TokenEntry token = new TokenEntry(processorName, segment, initialToken, serializer); entityManager.persist(token); } entityManager.flush(); }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException("Could not initialize segments. Some segments were already present."); } for (int segment = 0; segment < segmentCount; segment++) { tokens.put(new ProcessAndSegment(processorName, segment), getOrDefault(initialToken, NULL_TOKEN)); } }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { Connection connection = getConnection(); try { executeQuery(connection, c -> selectForUpdate(c, processorName, 0), resultSet -> { for (int segment = 0; segment < segmentCount; segment++) { insertTokenEntry(resultSet, initialToken, processorName, segment); } if (!connection.getAutoCommit()) { connection.commit(); } return null; }, e -> new UnableToClaimTokenException( "Could not initialize segments. Some segments were already present.", e )); } finally { closeQuietly(connection); } }
/** * Loads an existing {@link TokenEntry} or creates a new one using the given {@code entityManager} for given {@code * processorName} and {@code segment}. * * @param processorName the name of the event processor * @param segment the segment of the event processor * @param entityManager the entity manager instance to use for the query * @return the token entry for the given processor name and segment * @throws UnableToClaimTokenException if there is a token for given {@code processorName} and {@code segment}, but * it is claimed by another process. */ protected TokenEntry loadOrCreateToken(String processorName, int segment, EntityManager entityManager) { TokenEntry token = entityManager .find(TokenEntry.class, new TokenEntry.PK(processorName, segment), loadingLockMode); if (token == null) { token = new TokenEntry(processorName, segment, null, serializer); token.claim(nodeId, claimTimeout); entityManager.persist(token); // hibernate complains about updates in different transactions if this isn't flushed entityManager.flush(); } else if (!token.claim(nodeId, claimTimeout)) { throw new UnableToClaimTokenException( format("Unable to claim token '%s[%s]'. It is owned by '%s'", token.getProcessorName(), token.getSegment(), token.getOwner())); } return token; }
@Test public void testResetRejectedIfNotAllTokensCanBeClaimed() { tokenStore.initializeTokenSegments("test", 4); when(tokenStore.fetchToken("test", 3)).thenThrow(new UnableToClaimTokenException("Mock")); try { testSubject.resetTokens(); fail("Expected exception"); } catch (UnableToClaimTokenException e) { // expected } verify(tokenStore, never()).storeToken(isNull(), anyString(), anyInt()); }
/** * This processor won't be able to handle any segments, as claiming a segment will fail. */ @Test public void testProcessorWorkerCountWithMultipleSegmentsClaimFails() throws InterruptedException { tokenStore.storeToken(new GlobalSequenceTrackingToken(1L), "test", 0); tokenStore.storeToken(new GlobalSequenceTrackingToken(2L), "test", 1); // Will skip segments. doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).extendClaim("test", 0); doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).fetchToken("test", 0); doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).extendClaim("test", 1); doThrow(new UnableToClaimTokenException("Failed")).when(tokenStore).fetchToken("test", 1); testSubject.start(); // give it some time to split segments from the store and submit to executor service. Thread.sleep(200); assertWithin(1, SECONDS, () -> assertThat(testSubject.activeProcessorThreads(), is(0))); }
/** * Tries to claim the given token {@code entry}. If the claim fails an {@link UnableToClaimTokenException} should be * thrown. Otherwise the given {@code resultSet} should be updated to reflect the claim. * * @param resultSet the updatable query result of an executed {@link PreparedStatement} * @param entry the entry extracted from the given result set * @return the claimed tracking token * * @throws UnableToClaimTokenException if the token cannot be claimed because another node currently owns the token * @throws SQLException when an exception occurs while claiming the token entry */ protected TrackingToken claimToken(ResultSet resultSet, AbstractTokenEntry<?> entry) throws SQLException { if (!entry.claim(nodeId, claimTimeout)) { throw new UnableToClaimTokenException( format("Unable to claim token '%s[%s]'. It is owned by '%s'", entry.getProcessorName(), entry.getSegment(), entry.getOwner())); } resultSet.updateString(schema.ownerColum(), entry.getOwner()); resultSet.updateString(schema.timestampColumn(), entry.timestampAsString()); resultSet.updateRow(); return entry.getToken(serializer); }
@Override public TrackingToken fetchToken(final String processorName, final int segment) throws UnableToClaimTokenException { // segment is currently only 0 = root, but will be eventually used for partitioning/parallelization log.info("Fetch token {}, {}", processorName, segment); Optional<Long> offset = Optional.empty(); try { offset = Optional.of(consumer.endOffsets(Arrays.asList(this.topicPartition)).get(this.topicPartition)); } catch (Exception e) { log.error("Error claiming token", e); throw new UnableToClaimTokenException("Error claiming a token for processor " + processorName); } return new GlobalSequenceTrackingToken(offset.orElse(Long.valueOf(0)).longValue()); }
@Override public void extendClaim(String processorName, int segment) throws UnableToClaimTokenException { EntityManager entityManager = entityManagerProvider.getEntityManager(); int updates = entityManager.createQuery("UPDATE TokenEntry te SET te.timestamp = :timestamp " + "WHERE te.processorName = :processorName " + "AND te.segment = :segment " + "AND te.owner = :owner") .setParameter("processorName", processorName) .setParameter("segment", segment) .setParameter("owner", nodeId) .setParameter("timestamp", formatInstant(TokenEntry.clock.instant())) .executeUpdate(); if (updates == 0) { throw new UnableToClaimTokenException("Unable to extend the claim on token for processor '" + processorName + "[" + segment + "]'. It is either claimed " + "by another process, or there is no such token."); } }
@Override public void storeToken(final TrackingToken token, final String processorName, final int segment) throws UnableToClaimTokenException { // segment is currently only 0 = root, but will be eventually used for partitioning/parallelization log.info("Store token {} {}, {}", token, processorName, segment); Assert.isTrue(token == null || token instanceof GlobalSequenceTrackingToken, () -> String.format("Token [%s] is of the wrong type. Expected [%s]", token, GlobalSequenceTrackingToken.class.getSimpleName())); final long offset = ((GlobalSequenceTrackingToken) token).getGlobalIndex(); try { consumer.seek(topicPartition, offset); } catch (Exception e) { log.error("Error claiming token", e); throw new UnableToClaimTokenException("Error claiming a token for processor " + processorName); } }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { EntityManager entityManager = entityManagerProvider.getEntityManager(); if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException("Could not initialize segments. Some segments were already present."); } for (int segment = 0; segment < segmentCount; segment++) { TokenEntry token = new TokenEntry(processorName, segment, initialToken, serializer); entityManager.persist(token); } entityManager.flush(); }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { EntityManager entityManager = entityManagerProvider.getEntityManager(); if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException("Could not initialize segments. Some segments were already present."); } for (int segment = 0; segment < segmentCount; segment++) { TokenEntry token = new TokenEntry(processorName, segment, initialToken, serializer); entityManager.persist(token); } entityManager.flush(); }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException("Could not initialize segments. Some segments were already present."); } for (int segment = 0; segment < segmentCount; segment++) { tokens.put(new ProcessAndSegment(processorName, segment), getOrDefault(initialToken, NULL_TOKEN)); } }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException("Could not initialize segments. Some segments were already present."); } for (int segment = 0; segment < segmentCount; segment++) { tokens.put(new ProcessAndSegment(processorName, segment), getOrDefault(initialToken, NULL_TOKEN)); } }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException("Unable to initialize segments. Some tokens were already present for the given processor."); } List<Document> entries = IntStream.range(0, segmentCount) .mapToObj(segment -> new GenericTokenEntry<>(initialToken, serializer, contentType, processorName, segment)) .map(this::tokenEntryToDocument) .collect(Collectors.toList()); mongoTemplate.trackingTokensCollection() .insertMany(entries, new InsertManyOptions().ordered(false)); }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { if (fetchSegments(processorName).length > 0) { throw new UnableToClaimTokenException( "Unable to initialize segments. Some tokens were already present for the given processor." ); } List<Document> entries = IntStream.range(0, segmentCount) .mapToObj(segment -> new GenericTokenEntry<>(initialToken, serializer, contentType, processorName, segment)) .map(this::tokenEntryToDocument) .collect(Collectors.toList()); mongoTemplate.trackingTokensCollection() .insertMany(entries, new InsertManyOptions().ordered(false)); }
@Override public void initializeTokenSegments(String processorName, int segmentCount, TrackingToken initialToken) throws UnableToClaimTokenException { Connection connection = getConnection(); try { executeQuery(connection, c -> selectForUpdate(c, processorName, 0), resultSet -> { for (int segment = 0; segment < segmentCount; segment++) { insertTokenEntry(resultSet, initialToken, processorName, segment); } if (!connection.getAutoCommit()) { connection.commit(); } return null; }, e -> new UnableToClaimTokenException("Could not initialize segments. Some segments were already present.", e)); } finally { closeQuietly(connection); } }
@Override public void extendClaim(String processorName, int segment) throws UnableToClaimTokenException { UpdateResult updateResult = mongoTemplate.trackingTokensCollection() .updateOne(and(eq("processorName", processorName), eq("segment", segment), eq("owner", nodeId)), set("timestamp", TokenEntry.clock.instant().toEpochMilli())); if (updateResult.getMatchedCount() == 0) { throw new UnableToClaimTokenException(format("Unable to extend claim on token token '%s[%s]'. It is owned " + "by another segment.", processorName, segment)); } }
@Override public void extendClaim(String processorName, int segment) throws UnableToClaimTokenException { UpdateResult updateResult = mongoTemplate.trackingTokensCollection() .updateOne(and(eq("processorName", processorName), eq("segment", segment), eq("owner", nodeId)), set("timestamp", TokenEntry.clock.instant().toEpochMilli())); if (updateResult.getMatchedCount() == 0) { throw new UnableToClaimTokenException(format( "Unable to extend claim on token token '%s[%s]'. It is owned by another segment.", processorName, segment )); } }