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

io.camunda.zeebe.scheduler.ActorJob Maven / Gradle / Ivy

The newest version!
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Camunda License 1.0. You may not use this file
 * except in compliance with the Camunda License 1.0.
 */
package io.camunda.zeebe.scheduler;

import io.camunda.zeebe.scheduler.ActorTask.TaskSchedulingState;
import io.camunda.zeebe.scheduler.future.ActorFuture;
import io.camunda.zeebe.scheduler.future.CompletableActorFuture;
import io.camunda.zeebe.util.Loggers;
import io.camunda.zeebe.util.error.FatalErrorHandler;
import java.util.concurrent.Callable;
import org.jetbrains.annotations.Async;
import org.slf4j.Logger;

@SuppressWarnings({"unchecked", "rawtypes"})
public final class ActorJob {
  private static final Logger LOG = Loggers.ACTOR_LOGGER;
  private static final FatalErrorHandler FATAL_ERROR_HANDLER = FatalErrorHandler.withLogger(LOG);

  TaskSchedulingState schedulingState;
  ActorTask task;
  private Callable callable;
  private Runnable runnable;
  private ActorFuture resultFuture;
  private ActorSubscription subscription;
  private long scheduledAt = -1;

  public void onJobAddedToTask(final ActorTask task) {
    scheduledAt = System.nanoTime();
    this.task = task;
    schedulingState = TaskSchedulingState.QUEUED;
  }

  @Async.Execute
  void execute(final ActorThread runner) {
    observeSchedulingLatency(runner.getActorMetrics());
    try {
      invoke();
    } catch (final Throwable e) {
      FATAL_ERROR_HANDLER.handleError(e);
      task.onFailure(e);
    } finally {
      // in any case, success or exception, decide if the job should be resubmitted
      if (isTriggeredBySubscription() || runnable == null) {
        schedulingState = TaskSchedulingState.TERMINATED;
      } else {
        schedulingState = TaskSchedulingState.QUEUED;
        scheduledAt = System.nanoTime();
      }
    }
  }

  private void observeSchedulingLatency(final ActorMetrics metrics) {
    if (metrics.isEnabled()) {
      final var now = System.nanoTime();
      if (subscription instanceof final ActorFutureSubscription s
          && s.getFuture() instanceof final CompletableActorFuture f) {
        final var subscriptionCompleted = f.getCompletedAt();
        metrics.observeJobSchedulingLatency(now - subscriptionCompleted, "Future");
      } else if (subscription instanceof final TimerSubscription s) {
        final var timerExpired = s.getTimerExpiredAt();
        metrics.observeJobSchedulingLatency(now - timerExpired, "Timer");
      } else if (subscription == null && scheduledAt != -1) {
        metrics.observeJobSchedulingLatency(now - scheduledAt, "None");
      }
    }
  }

  private void invoke() throws Exception {
    final Object invocationResult;
    if (callable != null) {
      invocationResult = callable.call();
    } else {
      invocationResult = null;
      // only tasks triggered by a subscription can "yield"; everything else just executes once
      if (!isTriggeredBySubscription()) {
        final Runnable r = runnable;
        runnable = null;
        r.run();
      } else {
        runnable.run();
      }
    }
    if (resultFuture != null) {
      resultFuture.complete(invocationResult);
      resultFuture = null;
    }
  }

  public void setRunnable(final Runnable runnable) {
    this.runnable = runnable;
  }

  public ActorFuture setCallable(final Callable callable) {
    this.callable = callable;
    setResultFuture(new CompletableActorFuture<>());
    return resultFuture;
  }

  /** used to recycle the job object */
  void reset() {
    schedulingState = TaskSchedulingState.NOT_SCHEDULED;
    scheduledAt = -1;

    task = null;

    callable = null;
    runnable = null;

    resultFuture = null;
    subscription = null;
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder("ActorJob{");
    sb.append("schedulingState=").append(schedulingState);
    sb.append(", task=").append(task);
    if (callable != null) {
      sb.append(", callable=").append(callable);
    }
    if (runnable != null) {
      sb.append(", runnable=").append(runnable);
    }
    if (resultFuture != null) {
      sb.append(", resultFuture=").append(resultFuture);
    }
    if (subscription != null) {
      sb.append(", subscription=").append(subscription);
    }
    if (scheduledAt != -1) {
      sb.append(", scheduledAt=").append(scheduledAt);
    }
    sb.append('}');
    return sb.toString();
  }

  public boolean isTriggeredBySubscription() {
    return subscription != null;
  }

  public ActorSubscription getSubscription() {
    return subscription;
  }

  public void setSubscription(final ActorSubscription subscription) {
    this.subscription = subscription;
    task.addSubscription(subscription);
  }

  public ActorTask getTask() {
    return task;
  }

  public Actor getActor() {
    return task.actor;
  }

  public void setResultFuture(final ActorFuture resultFuture) {
    assert !resultFuture.isDone();
    this.resultFuture = resultFuture;
  }

  public void failFuture(final String reason) {
    failFuture(new RuntimeException(reason));
  }

  public void failFuture(final Throwable cause) {
    if (resultFuture != null) {
      resultFuture.completeExceptionally(cause);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy