Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion checkstyle/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@

<!-- Streams -->
<suppress checks="ClassFanOutComplexity"
files="(KafkaStreams|KStreamImpl|KTableImpl|InternalTopologyBuilder|StreamsPartitionAssignor).java"/>
files="(KafkaStreams|KStreamImpl|KTableImpl|InternalTopologyBuilder|StreamsPartitionAssignor|StreamThread).java"/>

<suppress checks="MethodLength"
files="KTableImpl.java"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.nio.file.StandardOpenOption;
import java.util.AbstractMap;
import java.util.EnumSet;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.kafka.common.KafkaException;
Expand Down Expand Up @@ -1328,6 +1329,10 @@ public static <E> Set<E> diff(final Supplier<Set<E>> constructor, final Set<E> l
return result;
}

public static <K, V> Map<K, V> filterMap(final Map<K, V> map, final Predicate<Entry<K, V>> filterPredicate) {
return map.entrySet().stream().filter(filterPredicate).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}

/**
* Convert a properties to map. All keys in properties must be string type. Otherwise, a ConfigException is thrown.
* @param properties to be converted
Expand Down
22 changes: 10 additions & 12 deletions streams/src/main/java/org/apache/kafka/streams/KafkaStreams.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public class KafkaStreams implements AutoCloseable {
private final Metrics metrics;
private final StreamsConfig config;
protected final List<StreamThread> threads;
private final StateDirectory stateDirectory;
protected final StateDirectory stateDirectory;
private final StreamsMetadataState streamsMetadataState;
private final ScheduledExecutorService stateDirCleaner;
private final ScheduledExecutorService rocksDBMetricsRecordingService;
Expand Down Expand Up @@ -349,12 +349,18 @@ public State state() {
return state;
}

private boolean isRunningOrRebalancing() {
protected boolean isRunningOrRebalancing() {
synchronized (stateLock) {
return state.isRunningOrRebalancing();
}
}

protected boolean hasStartedOrFinishedShuttingDown() {
synchronized (stateLock) {
return state.hasStartedOrFinishedShuttingDown();
}
}

private void validateIsRunningOrRebalancing() {
synchronized (stateLock) {
if (state.hasNotStarted()) {
Expand Down Expand Up @@ -875,7 +881,7 @@ private KafkaStreams(final TopologyMetadata topologyMetadata,
ClientMetrics.addVersionMetric(streamsMetrics);
ClientMetrics.addCommitIdMetric(streamsMetrics);
ClientMetrics.addApplicationIdMetric(streamsMetrics, config.getString(StreamsConfig.APPLICATION_ID_CONFIG));
ClientMetrics.addTopologyDescriptionMetric(streamsMetrics, this.topologyMetadata.topologyDescriptionString());
ClientMetrics.addTopologyDescriptionMetric(streamsMetrics, (metricsConfig, now) -> this.topologyMetadata.topologyDescriptionString());
ClientMetrics.addStateMetric(streamsMetrics, (metricsConfig, now) -> state);
ClientMetrics.addNumAliveStreamThreadMetric(streamsMetrics, (metricsConfig, now) -> getNumLiveStreamThreads());

Expand Down Expand Up @@ -1283,14 +1289,6 @@ public synchronized void start() throws IllegalStateException, StreamsException
} else {
throw new IllegalStateException("The client is either already started or already stopped, cannot re-start");
}

if (topologyMetadata.isEmpty()) {
if (setState(State.RUNNING)) {
log.debug("Transitioning directly to RUNNING for app with no named topologies");
} else {
throw new IllegalStateException("Unexpected error in transitioning KafkaStreams with empty processing topology to RUNNING");
}
}
}

/**
Expand Down Expand Up @@ -1597,7 +1595,7 @@ public <T> T store(final StoreQueryParameters<T> storeQueryParameters) {
* threads lock when looping threads.
* @param consumer handler
*/
private void processStreamThread(final Consumer<StreamThread> consumer) {
protected void processStreamThread(final Consumer<StreamThread> consumer) {
final List<StreamThread> copy = new ArrayList<>(threads);
for (final StreamThread thread : copy) consumer.accept(thread);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ public static void addApplicationIdMetric(final StreamsMetricsImpl streamsMetric
}

public static void addTopologyDescriptionMetric(final StreamsMetricsImpl streamsMetrics,
final String topologyDescription) {
streamsMetrics.addClientLevelImmutableMetric(
final Gauge<String> topologyDescription) {
streamsMetrics.addClientLevelMutableMetric(
TOPOLOGY_DESCRIPTION,
TOPOLOGY_DESCRIPTION_DESCRIPTION,
RecordingLevel.INFO,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.apache.kafka.common.utils.Utils.filterMap;
import static org.apache.kafka.streams.processor.internals.ClientUtils.getTaskProducerClientId;
import static org.apache.kafka.streams.processor.internals.ClientUtils.getThreadProducerClientId;
import static org.apache.kafka.streams.processor.internals.StreamThread.ProcessingMode.EXACTLY_ONCE_ALPHA;
Expand All @@ -64,6 +66,11 @@ class ActiveTaskCreator {
private final Map<TaskId, StreamsProducer> taskProducers;
private final StreamThread.ProcessingMode processingMode;

// Tasks may have been assigned for a NamedTopology that is not yet known by this host. When that occurs we stash
// these unknown tasks until either the corresponding NamedTopology is added and we can create them at last, or
// we receive a new assignment and they are revoked from the thread.
private final Map<TaskId, Set<TopicPartition>> unknownTasksToBeCreated = new HashMap<>();

ActiveTaskCreator(final TopologyMetadata topologyMetadata,
final StreamsConfig config,
final StreamsMetricsImpl streamsMetrics,
Expand Down Expand Up @@ -132,18 +139,33 @@ StreamsProducer threadProducer() {
return threadProducer;
}

void removeRevokedUnknownTasks(final Set<TaskId> revokedTasks) {
unknownTasksToBeCreated.keySet().removeAll(revokedTasks);
}

Map<TaskId, Set<TopicPartition>> uncreatedTasksForTopologies(final Set<String> currentTopologies) {
return filterMap(unknownTasksToBeCreated, t -> currentTopologies.contains(t.getKey().namedTopology()));
}

// TODO: change return type to `StreamTask`
Collection<Task> createTasks(final Consumer<byte[], byte[]> consumer,
final Map<TaskId, Set<TopicPartition>> tasksToBeCreated) {
// TODO: change type to `StreamTask`
final List<Task> createdTasks = new ArrayList<>();
final Map<TaskId, Set<TopicPartition>> newUnknownTasks = new HashMap<>();

for (final Map.Entry<TaskId, Set<TopicPartition>> newTaskAndPartitions : tasksToBeCreated.entrySet()) {
final TaskId taskId = newTaskAndPartitions.getKey();
final Set<TopicPartition> partitions = newTaskAndPartitions.getValue();

final LogContext logContext = getLogContext(taskId);

final ProcessorTopology topology = topologyMetadata.buildSubtopology(taskId);
if (topology == null) {
// task belongs to a named topology that hasn't been added yet, wait until it has to create this
newUnknownTasks.put(taskId, partitions);
continue;
}

final ProcessorStateManager stateManager = new ProcessorStateManager(
taskId,
Expand Down Expand Up @@ -175,10 +197,16 @@ Collection<Task> createTasks(final Consumer<byte[], byte[]> consumer,
context
)
);
unknownTasksToBeCreated.remove(taskId);
}
if (!newUnknownTasks.isEmpty()) {
log.info("Delaying creation of tasks not yet known by this instance: {}", newUnknownTasks.keySet());
unknownTasksToBeCreated.putAll(newUnknownTasks);
}
return createdTasks;
}


StreamTask createActiveTaskFromStandby(final StandbyTask standbyTask,
final Set<TopicPartition> inputPartitions,
final Consumer<byte[], byte[]> consumer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.kafka.streams.processor.TopicNameExtractor;
import org.apache.kafka.streams.processor.api.ProcessorSupplier;
import org.apache.kafka.streams.processor.internals.TopologyMetadata.Subtopology;
import org.apache.kafka.streams.processor.internals.namedtopology.NamedTopology;
import org.apache.kafka.streams.state.StoreBuilder;
import org.apache.kafka.streams.state.internals.SessionStoreBuilder;
import org.apache.kafka.streams.state.internals.TimestampedWindowStoreBuilder;
Expand Down Expand Up @@ -136,6 +137,7 @@ public class InternalTopologyBuilder {

// The name of the topology this builder belongs to, or null if none
private String topologyName;
private NamedTopology namedTopology;

private boolean hasPersistentStores = false;

Expand Down Expand Up @@ -336,13 +338,17 @@ Sink<KIn, VIn> describe() {
}
}

public void setTopologyName(final String namedTopology) {
Objects.requireNonNull(namedTopology, "named topology can't be null");

public void setNamedTopology(final NamedTopology topology) {
final String topologyName = topology.name();
Objects.requireNonNull(topologyName, "topology name can't be null");
Objects.requireNonNull(topology, "named topology can't be null");
if (this.topologyName != null) {
log.error("Tried to reset the namedTopology to {} but it was already set to {}", namedTopology, this.topologyName);
throw new IllegalStateException("NamedTopology has already been set to " + this.topologyName);
log.error("Tried to reset the topologyName to {} but it was already set to {}", topologyName, this.topologyName);
throw new IllegalStateException("The topologyName has already been set to " + this.topologyName);
}
this.topologyName = namedTopology;
this.namedTopology = topology;
this.topologyName = topologyName;
}

// public for testing only
Expand All @@ -368,6 +374,10 @@ public String topologyName() {
return topologyName;
}

public NamedTopology namedTopology() {
return namedTopology;
}

public synchronized final InternalTopologyBuilder rewriteTopology(final StreamsConfig config) {
Objects.requireNonNull(config, "config can't be null");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;

import static org.apache.kafka.common.utils.Utils.filterMap;

class StandbyTaskCreator {
private final TopologyMetadata topologyMetadata;
Expand All @@ -43,6 +48,9 @@ class StandbyTaskCreator {
private final Logger log;
private final Sensor createTaskSensor;

// tasks may be assigned for a NamedTopology that is not yet known by this host, and saved for later creation
private final Map<TaskId, Set<TopicPartition>> unknownTasksToBeCreated = new HashMap<>();

StandbyTaskCreator(final TopologyMetadata topologyMetadata,
final StreamsConfig config,
final StreamsMetricsImpl streamsMetrics,
Expand All @@ -66,14 +74,30 @@ class StandbyTaskCreator {
);
}

void removeRevokedUnknownTasks(final Set<TaskId> revokedTasks) {
unknownTasksToBeCreated.keySet().removeAll(revokedTasks);
}

Map<TaskId, Set<TopicPartition>> uncreatedTasksForTopologies(final Set<String> currentTopologies) {
return filterMap(unknownTasksToBeCreated, t -> currentTopologies.contains(t.getKey().namedTopology()));
}

// TODO: change return type to `StandbyTask`
Collection<Task> createTasks(final Map<TaskId, Set<TopicPartition>> tasksToBeCreated) {
// TODO: change type to `StandbyTask`
final List<Task> createdTasks = new ArrayList<>();
final Map<TaskId, Set<TopicPartition>> newUnknownTasks = new HashMap<>();

for (final Map.Entry<TaskId, Set<TopicPartition>> newTaskAndPartitions : tasksToBeCreated.entrySet()) {
final TaskId taskId = newTaskAndPartitions.getKey();
final Set<TopicPartition> partitions = newTaskAndPartitions.getValue();

final ProcessorTopology topology = topologyMetadata.buildSubtopology(taskId);
if (topology == null) {
// task belongs to a named topology that hasn't been added yet, wait until it has to create this
newUnknownTasks.put(taskId, partitions);
continue;
}

if (topology.hasStateWithChangelogs()) {
final ProcessorStateManager stateManager = new ProcessorStateManager(
Expand Down Expand Up @@ -103,7 +127,11 @@ Collection<Task> createTasks(final Map<TaskId, Set<TopicPartition>> tasksToBeCre
taskId, partitions
);
}

unknownTasksToBeCreated.remove(taskId);
}
if (!newUnknownTasks.isEmpty()) {
log.info("Delaying creation of tasks not yet known by this instance: {}", newUnknownTasks.keySet());
unknownTasksToBeCreated.putAll(newUnknownTasks);
}
return createdTasks;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,16 @@ private File getTaskDirectoryParentName(final TaskId taskId) {
if (!hasNamedTopologies) {
throw new IllegalStateException("Tried to lookup taskId with named topology, but StateDirectory thinks hasNamedTopologies = false");
}
return new File(stateDir, "__" + namedTopology + "__");
return new File(stateDir, getNamedTopologyDirName(namedTopology));
} else {
return stateDir;
}
}

private String getNamedTopologyDirName(final String topologyName) {
return "__" + topologyName + "__";
}

/**
* @return The File handle for the checkpoint in the given task's directory
*/
Expand Down Expand Up @@ -517,6 +521,24 @@ private IOException maybeCleanEmptyNamedTopologyDirs(final boolean logExceptionA
return firstException.get();
}

/**
* Clears out any local state found for the given NamedTopology after it was removed
*
* @throws StreamsException if cleanup failed
*/
public void clearLocalStateForNamedTopology(final String topologyName) {
final File namedTopologyDir = new File(stateDir, getNamedTopologyDirName(topologyName));
if (!namedTopologyDir.exists() || !namedTopologyDir.isDirectory()) {
log.debug("Tried to clear out the local state for NamedTopology {} but none was found", topologyName);
}
try {
Utils.delete(namedTopologyDir);
} catch (final IOException e) {
log.error("Hit an unexpected error while clearing local state for NamedTopology {}", topologyName);
throw new StreamsException("Unable to delete state for the named topology " + topologyName);
}
}

private void cleanStateAndTaskDirectoriesCalledByUser() throws Exception {
if (!lockedTasksToOwner.isEmpty()) {
log.warn("Found some still-locked task directories when user requested to cleaning up the state, "
Expand Down
Loading