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

com.microsoft.durabletask.DurableTaskGrpcWorker Maven / Gradle / Ivy

Go to download

This package contains classes and interfaces for building Durable Task orchestrations in Java.

There is a newer version: 1.5.0
Show newest version
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.durabletask;

import com.google.protobuf.StringValue;

import com.microsoft.durabletask.implementation.protobuf.TaskHubSidecarServiceGrpc;
import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.*;
import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.WorkItem.RequestCase;
import com.microsoft.durabletask.implementation.protobuf.TaskHubSidecarServiceGrpc.*;

import io.grpc.*;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Task hub worker that connects to a sidecar process over gRPC to execute orchestrator and activity events.
 */
public final class DurableTaskGrpcWorker implements AutoCloseable {
    private static final int DEFAULT_PORT = 4001;
    private static final Logger logger = Logger.getLogger(DurableTaskGrpcWorker.class.getPackage().getName());

    private final HashMap orchestrationFactories = new HashMap<>();
    private final HashMap activityFactories = new HashMap<>();

    private final ManagedChannel managedSidecarChannel;
    private final DataConverter dataConverter;

    private final TaskHubSidecarServiceBlockingStub sidecarClient;

    DurableTaskGrpcWorker(DurableTaskGrpcWorkerBuilder builder) {
        this.orchestrationFactories.putAll(builder.orchestrationFactories);
        this.activityFactories.putAll(builder.activityFactories);

        Channel sidecarGrpcChannel;
        if (builder.channel != null) {
            // The caller is responsible for managing the channel lifetime
            this.managedSidecarChannel = null;
            sidecarGrpcChannel = builder.channel;
        } else {
            // Construct our own channel using localhost + a port number
            int port = DEFAULT_PORT;
            if (builder.port > 0) {
                port = builder.port;
            }

            // Need to keep track of this channel so we can dispose it on close()
            this.managedSidecarChannel = ManagedChannelBuilder
                    .forAddress("127.0.0.1", port)
                    .usePlaintext()
                    .build();
            sidecarGrpcChannel = this.managedSidecarChannel;
        }

        this.sidecarClient = TaskHubSidecarServiceGrpc.newBlockingStub(sidecarGrpcChannel);
        this.dataConverter = builder.dataConverter != null ? builder.dataConverter : new JacksonDataConverter();
    }

    /**
     * Establishes a gRPC connection to the sidecar and starts processing work-items in the background.
     * 

* This method retries continuously to establish a connection to the sidecar. If a connection fails, * a warning log message will be written and a new connection attempt will be made. This process * continues until either a connection succeeds or the process receives an interrupt signal. */ public void start() { new Thread(this::startAndBlock).start(); } /** * Closes the internally managed gRPC channel, if one exists. *

* This method is a no-op if this client object was created using a builder with a gRPC channel object explicitly * configured. */ public void close() { if (this.managedSidecarChannel != null) { try { this.managedSidecarChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { // Best effort. Also note that AutoClose documentation recommends NOT having // close() methods throw InterruptedException: // https://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html } } } private String getSidecarAddress() { return this.sidecarClient.getChannel().authority(); } /** * Establishes a gRPC connection to the sidecar and starts processing work-items on the current thread. * This method call blocks indefinitely, or until the current thread is interrupted. *

* Use can alternatively use the {@link #start} method to run orchestration processing in a background thread. *

* This method retries continuously to establish a connection to the sidecar. If a connection fails, * a warning log message will be written and a new connection attempt will be made. This process * continues until either a connection succeeds or the process receives an interrupt signal. */ public void startAndBlock() { logger.log(Level.INFO, "Durable Task worker is connecting to sidecar at {0}.", this.getSidecarAddress()); TaskOrchestrationExecutor taskOrchestrationExecutor = new TaskOrchestrationExecutor( this.orchestrationFactories, this.dataConverter, logger); TaskActivityExecutor taskActivityExecutor = new TaskActivityExecutor( this.activityFactories, this.dataConverter, logger); // TODO: How do we interrupt manually? while (true) { try { GetWorkItemsRequest getWorkItemsRequest = GetWorkItemsRequest.newBuilder().build(); Iterator workItemStream = this.sidecarClient.getWorkItems(getWorkItemsRequest); while (workItemStream.hasNext()) { WorkItem workItem = workItemStream.next(); RequestCase requestType = workItem.getRequestCase(); if (requestType == RequestCase.ORCHESTRATORREQUEST) { OrchestratorRequest orchestratorRequest = workItem.getOrchestratorRequest(); // TODO: Run this on a worker pool thread: https://www.baeldung.com/thread-pool-java-and-guava // TODO: Error handling TaskOrchestratorResult taskOrchestratorResult = taskOrchestrationExecutor.execute( orchestratorRequest.getPastEventsList(), orchestratorRequest.getNewEventsList()); OrchestratorResponse response = OrchestratorResponse.newBuilder() .setInstanceId(orchestratorRequest.getInstanceId()) .addAllActions(taskOrchestratorResult.getActions()) .setCustomStatus(StringValue.of(taskOrchestratorResult.getCustomStatus())) .build(); this.sidecarClient.completeOrchestratorTask(response); } else if (requestType == RequestCase.ACTIVITYREQUEST) { ActivityRequest activityRequest = workItem.getActivityRequest(); // TODO: Run this on a worker pool thread: https://www.baeldung.com/thread-pool-java-and-guava String output = null; TaskFailureDetails failureDetails = null; try { output = taskActivityExecutor.execute( activityRequest.getName(), activityRequest.getInput().getValue(), activityRequest.getTaskId()); } catch (Throwable e) { failureDetails = TaskFailureDetails.newBuilder() .setErrorType(e.getClass().getName()) .setErrorMessage(e.getMessage()) .setStackTrace(StringValue.of(FailureDetails.getFullStackTrace(e))) .build(); } ActivityResponse.Builder responseBuilder = ActivityResponse.newBuilder() .setInstanceId(activityRequest.getOrchestrationInstance().getInstanceId()) .setTaskId(activityRequest.getTaskId()); if (output != null) { responseBuilder.setResult(StringValue.of(output)); } if (failureDetails != null) { responseBuilder.setFailureDetails(failureDetails); } this.sidecarClient.completeActivityTask(responseBuilder.build()); } else { logger.log(Level.WARNING, "Received and dropped an unknown '{0}' work-item from the sidecar.", requestType); } } } catch (StatusRuntimeException e) { if (e.getStatus().getCode() == Status.Code.UNAVAILABLE) { logger.log(Level.INFO, "The sidecar at address {0} is unavailable. Will continue retrying.", this.getSidecarAddress()); } else if (e.getStatus().getCode() == Status.Code.CANCELLED) { logger.log(Level.INFO, "Durable Task worker has disconnected from {0}.", this.getSidecarAddress()); } else { logger.log(Level.WARNING, "Unexpected failure connecting to {0}.", this.getSidecarAddress()); } // Retry after 5 seconds try { Thread.sleep(5000); } catch (InterruptedException ex) { break; } } } } /** * Stops the current worker's listen loop, preventing any new orchestrator or activity events from being processed. */ public void stop() { this.close(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy