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

dev.responsive.kafka.api.async.internals.contexts.StreamThreadProcessorContext Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2024 Responsive Computing, Inc.
 *
 * This source code is licensed under the Responsive Business Source License Agreement v1.0
 * available at:
 *
 * https://www.responsive.dev/legal/responsive-bsl-10
 *
 * This software requires a valid Commercial License Key for production use. Trial and commercial
 * licenses can be obtained at https://www.responsive.dev
 */

package dev.responsive.kafka.api.async.internals.contexts;

import dev.responsive.kafka.api.async.internals.events.AsyncEvent;
import dev.responsive.kafka.api.async.internals.events.AsyncEvent.State;
import dev.responsive.kafka.api.async.internals.events.DelayedForward;
import dev.responsive.kafka.api.async.internals.events.DelayedWrite;
import dev.responsive.kafka.api.async.internals.stores.AsyncKeyValueStore;
import dev.responsive.kafka.api.async.internals.stores.AsyncTimestampedKeyValueStore;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.kafka.common.utils.LogContext;
import org.apache.kafka.streams.processor.StateStore;
import org.apache.kafka.streams.processor.internals.InternalProcessorContext;
import org.apache.kafka.streams.processor.internals.ProcessorNode;
import org.apache.kafka.streams.processor.internals.ProcessorRecordContext;
import org.apache.kafka.streams.state.KeyValueStore;
import org.apache.kafka.streams.state.TimestampedKeyValueStore;
import org.slf4j.Logger;

/**
 * A wrapper around the original processor context to be used by the StreamThread.
 * This context handles everything related to initialization of the processor
 * (eg {@link #getStateStore(String)}) as well as the "finalization" of async events,
 * ie preparing for (and executing) delayed forwards or writes.
 * Delayed forwards are intercepted by the other kind of async context, which is
 * unique to a given AsyncThread, called the {@link AsyncThreadProcessorContext}
 * 

* Threading notes: * -For use by StreamThreads only * -One per physical AsyncProcessor * (ie one per async processor per partition per StreamThread) */ public class StreamThreadProcessorContext extends DelegatingProcessorContext> { private final Logger log; private final Map> storeNameToAsyncStore = new HashMap<>(); private final ProcessorNode asyncProcessorNode; private final InternalProcessorContext originalContext; private final DelayedAsyncStoreWriter delayedStoreWriter; public StreamThreadProcessorContext( final String logPrefix, final InternalProcessorContext originalContext, final DelayedAsyncStoreWriter delayedStoreWriter ) { super(); this.log = new LogContext(Objects.requireNonNull(logPrefix)) .logger(StreamThreadProcessorContext.class); this.originalContext = Objects.requireNonNull(originalContext); this.asyncProcessorNode = originalContext.currentNode(); this.delayedStoreWriter = Objects.requireNonNull(delayedStoreWriter); } @Override @SuppressWarnings("unchecked") public S getStateStore(final String name) { if (storeNameToAsyncStore.containsKey(name)) { return (S) storeNameToAsyncStore.get(name); } final S userDelegate = super.getStateStore(name); if (userDelegate instanceof TimestampedKeyValueStore) { final var asyncStore = new AsyncTimestampedKeyValueStore<>( name, taskId().partition(), (KeyValueStore) userDelegate, delayedStoreWriter ); storeNameToAsyncStore.put(name, asyncStore); return (S) asyncStore; } else if (userDelegate instanceof KeyValueStore) { final var asyncStore = new AsyncKeyValueStore<>( name, originalContext.partition(), (KeyValueStore) userDelegate, delayedStoreWriter ); storeNameToAsyncStore.put(name, asyncStore); return (S) asyncStore; } else { log.error("Attempted to connect window/session store with async processor"); throw new UnsupportedOperationException( "Window and Session stores are not yet supported with async processing"); } } /** * (Re)set all inner state and metadata to prepare for a delayed async execution * such as processing input records or forwarding output records */ public PreviousRecordContextAndNode prepareToFinalizeEvent(final AsyncEvent event) { if (!event.currentState().equals(State.TO_FINALIZE)) { log.error("Attempted to prepare event for finalization but currentState was {}", event.currentState()); throw new IllegalStateException( "Must prepare event for finalization while it's in the TO_FINALIZE state" ); } final ProcessorRecordContext recordContext = event.recordContext(); // Note: the "RecordContext" and "RecordMetadata" refer to/are the same thing, and // even though they have separate getters with slightly different return types, they // both ultimately just return the recordContext we set here. So we don't need to // worry about setting the recordMetadata separately, even though #recordMetadata is // exposed to the user, since #setRecordContext takes care of that final PreviousRecordContextAndNode previousRecordContextAndNode = new PreviousRecordContextAndNode( originalContext.recordContext(), originalContext.currentNode(), originalContext ); originalContext.setRecordContext(recordContext); originalContext.setCurrentNode(asyncProcessorNode); return previousRecordContextAndNode; } public void executeDelayedWrite( final DelayedWrite delayedWrite ) { final AsyncKeyValueStore asyncStore = getAsyncStore(delayedWrite.storeName()); asyncStore.executeDelayedWrite(delayedWrite); } public void executeDelayedForward( final DelayedForward delayedForward ) { if (delayedForward.isFixedKey()) { super.forward(delayedForward.fixedKeyRecord(), delayedForward.childName()); } else { super.forward(delayedForward.record(), delayedForward.childName()); } } @Override public InternalProcessorContext delegate() { return originalContext; } @SuppressWarnings("unchecked") public AsyncKeyValueStore getAsyncStore(final String storeName) { return (AsyncKeyValueStore) storeNameToAsyncStore.get(storeName); } public Map> getAllAsyncStores() { return storeNameToAsyncStore; } public static class PreviousRecordContextAndNode implements AutoCloseable { private final ProcessorRecordContext context; private final ProcessorNode node; private final InternalProcessorContext previousContext; public PreviousRecordContextAndNode( final ProcessorRecordContext context, final ProcessorNode node, final InternalProcessorContext previousContext) { this.context = context; this.node = node; this.previousContext = previousContext; } @Override public void close() { previousContext.setRecordContext(context); previousContext.setCurrentNode(node); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy