All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fluxcapacitor.javaclient.tracking.client.DefaultTracker Maven / Gradle / Ivy

There is a newer version: 0.1015.0
Show newest version
/*
 * Copyright (c) 2016-2017 Flux Capacitor.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.fluxcapacitor.javaclient.tracking.client;

import io.fluxcapacitor.common.Registration;
import io.fluxcapacitor.common.api.SerializedMessage;
import io.fluxcapacitor.common.api.tracking.MessageBatch;
import io.fluxcapacitor.javaclient.tracking.Tracker;
import io.fluxcapacitor.javaclient.tracking.TrackingConfiguration;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import static io.fluxcapacitor.common.TimingUtils.retryOnFailure;
import static io.fluxcapacitor.javaclient.tracking.BatchInterceptor.join;
import static java.lang.Thread.currentThread;

/**
 * A tracker keeps reading messages until it is stopped (generally only when the application is shut down).
 * 

* A tracker is always running in a single thread. To balance the processing load over multiple threads create multiple * trackers with the same name but different tracker id. *

* Trackers with different names will receive the same messages. Trackers with the same name will not. (Flux Capacitor * will load balance between trackers with the same name). *

* Tracking stops if the provided message consumer throws an exception while handling messages (i.e. the tracker will * need to be manually restarted in that case). However, if the tracker encounters an exception while fetching messages * it will retry fetching indefinitely until this succeeds. *

* Trackers can choose a desired maximum batch size for consuming. By default this batch size will be the same as the * batch size the tracker uses to fetch messages from Flux Capacitor. Each time the consumer has finished consuming a * batch the tracker will update its position with Flux Capacitor. *

* Trackers can be configured to use batch interceptors. A batch interceptor manages the invocation of the message * consumer. It is therefore typically used to manage a database transaction around the invocation of the consumer. Note * that if the interceptor gives rise to an exception the tracker will be stopped. */ @Slf4j public class DefaultTracker implements Runnable, Registration { private final String name; private final String trackerId; private final TrackingConfiguration configuration; private final Consumer processor; private final Consumer> consumer; private final TrackingClient trackingClient; private final AtomicBoolean running = new AtomicBoolean(); private final AtomicReference thread = new AtomicReference<>(); private volatile boolean processing; public DefaultTracker(String name, String trackerId, TrackingConfiguration configuration, Consumer> consumer, TrackingClient trackingClient) { this.name = name; this.trackerId = trackerId; this.configuration = configuration; this.processor = join(configuration.getBatchInterceptors()).intercept(this::processAll, new Tracker(name, trackerId)); this.consumer = consumer; this.trackingClient = trackingClient; } @Override public void run() { if (running.compareAndSet(false, true)) { thread.set(currentThread()); MessageBatch batch = fetch(null); Long lastKnownIndex = batch.getLastIndex(); while (running.get()) { processor.accept(batch); batch = fetch(lastKnownIndex); if (batch.getLastIndex() != null) { lastKnownIndex = batch.getLastIndex(); } } } } @Override public void cancel() { if (running.compareAndSet(true, false)) { //wait for processing to complete if (processing) { while (processing) { try { Thread.sleep(1); } catch (InterruptedException e) { currentThread().interrupt(); return; } } } else { //interrupt message fetching try { thread.get().interrupt(); } catch (Exception e) { log.warn("Not allowed to cancel tracker {}", name, e); } finally { thread.set(null); } } } } protected MessageBatch fetch(Long lastIndex) { return retryOnFailure(() -> trackingClient.readAndWait(name, trackerId, lastIndex, configuration), configuration.getRetryDelay(), e -> running.get()); } protected void processAll(MessageBatch messageBatch) { try { processing = true; List messages = messageBatch.getMessages(); if (messages.isEmpty() || !running.get()) { return; } if (messages.size() > configuration.getMaxConsumerBatchSize()) { for (int i = 0; i < messages.size(); i += configuration.getMaxConsumerBatchSize()) { List batch = messages.subList(i, Math.min(i + configuration.getMaxConsumerBatchSize(), messages.size())); processPart(batch, messageBatch.getSegment()); } } else { processPart(messages, messageBatch.getSegment()); } } finally { processing = false; } } protected void processPart(List batch, int[] segment) { try { consumer.accept(batch); } catch (Exception e) { log.error("Consumer {} failed to handle batch of {} messages and did not handle exception. " + "Tracker will be stopped.", name, batch.size(), e); cancel(); throw e; } retryOnFailure(() -> updatePosition(segment, batch.get(batch.size() - 1).getIndex()), configuration.getRetryDelay(), e -> running.get()); } @SneakyThrows private void updatePosition(int[] segment, Long lastIndex) { trackingClient.storePosition(name, segment, lastIndex).await(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy