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

io.camunda.zeebe.scheduler.ActorControl 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 static io.camunda.zeebe.scheduler.ActorThread.ensureCalledFromActorThread;

import io.camunda.zeebe.scheduler.ActorTask.ActorLifecyclePhase;
import io.camunda.zeebe.scheduler.future.ActorFuture;
import io.camunda.zeebe.scheduler.future.AllCompletedFutureConsumer;
import io.camunda.zeebe.scheduler.future.FutureContinuationRunnable;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

public class ActorControl implements ConcurrencyControl {
  final ActorTask task;
  private final Actor actor;

  public ActorControl(final Actor actor) {
    this.actor = actor;
    task = new ActorTask(actor);
  }

  private ActorControl(final ActorTask task) {
    actor = task.actor;
    this.task = task;
  }

  public static ActorControl current() {
    final ActorThread actorThread = ensureCalledFromActorThread("ActorControl#current");

    return new ActorControl(actorThread.currentTask);
  }

  /**
   * Conditional actions are called while the actor is in the following actor lifecycle phases:
   * {@link ActorLifecyclePhase#STARTED}
   *
   * @param conditionName
   * @param conditionAction
   * @return
   */
  public ActorCondition onCondition(final String conditionName, final Runnable conditionAction) {
    ensureCalledFromWithinActor("onCondition(...)");

    final ActorJob job = new ActorJob();
    job.setRunnable(conditionAction);
    job.onJobAddedToTask(task);

    final ActorConditionImpl condition = new ActorConditionImpl(conditionName, job);
    job.setSubscription(condition);

    return condition;
  }

  /**
   * Callables actions are called while the actor is in the following actor lifecycle phases: {@link
   * ActorLifecyclePhase#STARTED}
   *
   * @param action
   * @return
   */
  public ActorFuture call(final Runnable action) {
    final Callable c =
        () -> {
          action.run();
          return null;
        };

    return call(c);
  }

  /**
   * Scheduled a repeating timer
   *
   * 

The runnable is executed while the actor is in the following actor lifecycle phases: {@link * ActorLifecyclePhase#STARTED} * * @param delay * @param runnable * @return */ public ScheduledTimer runAtFixedRate(final Duration delay, final Runnable runnable) { ensureCalledFromWithinActor("runAtFixedRate(...)"); return scheduleTimerSubscription( runnable, job -> new DelayedTimerSubscription(job, delay.toMillis(), TimeUnit.MILLISECONDS, true)); } /** * Schedule a timer task at (or after) a timestamp. * *

The runnable is executed while the actor is in the following actor lifecycle phases: {@link * * ActorLifecyclePhase#STARTED} * *

This provides no guarantees that the timer task is run at the timestamp. It's likely that * the timer task is run shortly after the timestamp. We guarantee that the runnable won't run * before the timestamp. * * @param timestamp A unix epoch timestamp in milliseconds * @param runnable The runnable to run at (or after) the timestamp * @return A handle to the scheduled timer task */ public ScheduledTimer runAt(final long timestamp, final Runnable runnable) { ensureCalledFromWithinActor("runAt(...)"); return scheduleTimerSubscription(runnable, job -> new StampedTimerSubscription(job, timestamp)); } private TimerSubscription scheduleTimerSubscription( final Runnable runnable, final Function subscriptionFactory) { final ActorJob job = new ActorJob(); job.setRunnable(runnable); job.onJobAddedToTask(task); final TimerSubscription timerSubscription = subscriptionFactory.apply(job); job.setSubscription(timerSubscription); timerSubscription.submit(); return timerSubscription; } /** * Invoke the callback when the given future is completed (successfully or exceptionally). This * call does not block the actor. If close is requested the actor will not wait on this future, in * this case the callback is never called. * *

The callback is executed while the actor is in the following actor lifecycle phases: {@link * ActorLifecyclePhase#STARTED} * * @param future the future to wait on * @param callback the callback that handle the future's result. The throwable is null * when the future is completed successfully. */ @Override public void runOnCompletion( final ActorFuture future, final BiConsumer callback) { ensureCalledFromWithinActor("runOnCompletion(...)"); final ActorLifecyclePhase lifecyclePhase = task.getLifecyclePhase(); if (lifecyclePhase != ActorLifecyclePhase.CLOSE_REQUESTED && lifecyclePhase != ActorLifecyclePhase.CLOSED) { submitContinuationJob( future, callback, (job) -> new ActorFutureSubscription(future, job, lifecyclePhase.getValue())); } } /** * Runnables submitted by the actor itself are executed while the actor is in any of its lifecycle * phases. * *

Runnables submitted externally are executed while the actor is in the following actor * lifecycle phases: {@link ActorLifecyclePhase#STARTED} * * @param action */ @Override public void run(final Runnable action) { scheduleRunnable(action); } /** * Callables actions are called while the actor is in the following actor lifecycle phases: {@link * ActorLifecyclePhase#STARTED} * * @param callable * @return */ @Override @SuppressWarnings("unchecked") public ActorFuture call(final Callable callable) { final ActorThread runner = ActorThread.current(); if (runner != null && runner.getCurrentTask() == task) { throw new UnsupportedOperationException( "Incorrect usage of actor.call(...) cannot be called from current actor."); } final ActorJob job = new ActorJob(); final ActorFuture future = job.setCallable(callable); job.onJobAddedToTask(task); task.submit(job); return future; } /** * The runnable is executed while the actor is in the following actor lifecycle phases: {@link * ActorLifecyclePhase#STARTED} * * @param delay * @param runnable * @return */ @Override public ScheduledTimer schedule(final Duration delay, final Runnable runnable) { ensureCalledFromWithinActor("runDelayed(...)"); return scheduleTimerSubscription( runnable, job -> new DelayedTimerSubscription(job, delay.toMillis(), TimeUnit.MILLISECONDS, false)); } /** * Like {@link #run(Runnable)} but submits the runnable to the end of the actor's queue such that * other actions may be executed before this. This method is useful in case an actor is in a * (potentially endless) loop, and it should be able to interrupt it. * *

The runnable is executed while the actor is in the following actor lifecycle phases: {@link * ActorLifecyclePhase#STARTED} * * @param action the action to run. */ public void submit(final Runnable action) { final ActorThread currentThread = ActorThread.current(); final ActorTask currentTask = currentThread == null ? null : currentThread.getCurrentTask(); final ActorJob job; if (currentThread != null && currentTask == task) { job = currentThread.newJob(); } else { job = new ActorJob(); } job.setRunnable(action); job.onJobAddedToTask(task); task.submit(job); if (currentTask != null && currentTask == task) { yieldThread(); } } /** * Invoke the callback when the given future is completed (successfully or exceptionally). This * call does not block the actor. If close is requested the actor will wait on this future and not * change the phase, in this case the callback will eventually be called. * *

The callback is executed while the actor is in the following actor lifecycle phases: {@link * ActorLifecyclePhase#STARTED} * * @param future the future to wait on * @param callback the callback that handle the future's result. The throwable is null * when the future is completed successfully. */ public void runOnCompletionBlockingCurrentPhase( final ActorFuture future, final BiConsumer callback) { ensureCalledFromWithinActor("runOnCompletionBlockingCurrentPhase(...)"); final ActorLifecyclePhase lifecyclePhase = task.getLifecyclePhase(); if (lifecyclePhase != ActorLifecyclePhase.CLOSED) { submitContinuationJob( future, callback, (job) -> new ActorFutureSubscription( future, job, (task.getLifecyclePhase().getValue() | ActorLifecyclePhase.CLOSE_REQUESTED.getValue()))); } } private void submitContinuationJob( final ActorFuture future, final BiConsumer callback, final Function futureSubscriptionSupplier) { final ActorJob continuationJob = new ActorJob(); continuationJob.setRunnable(new FutureContinuationRunnable<>(future, callback)); continuationJob.onJobAddedToTask(task); final ActorFutureSubscription subscription = futureSubscriptionSupplier.apply(continuationJob); continuationJob.setSubscription(subscription); future.block(task); } /** * Invoke the callback when the given futures are completed (successfully or exceptionally). This * call does not block the actor. * *

The callback is executed while the actor is in the following actor lifecycle phases: {@link * ActorLifecyclePhase#STARTED} * * @param futures the futures to wait on * @param callback The throwable is null when all futures are completed successfully. * Otherwise, it holds the exception of the last completed future. */ @Override public void runOnCompletion( final Collection> futures, final Consumer callback) { if (!futures.isEmpty()) { final BiConsumer futureConsumer = new AllCompletedFutureConsumer<>(futures.size(), callback); for (final ActorFuture future : futures) { runOnCompletion(future, futureConsumer); } } else { callback.accept(null); } } /** can be called by the actor to yield the thread */ public void yieldThread() { final ActorJob job = ensureCalledFromWithinActor("yieldThread()"); job.getTask().yieldThread(); } public ActorFuture close() { final ActorJob closeJob = new ActorJob(); closeJob.onJobAddedToTask(task); closeJob.setRunnable(task::requestClose); task.submit(closeJob); return task.closeFuture; } private void scheduleRunnable(final Runnable runnable) { final ActorThread currentActorThread = ActorThread.current(); if (currentActorThread != null && currentActorThread.getCurrentTask() == task) { final ActorJob newJob = currentActorThread.newJob(); newJob.setRunnable(runnable); newJob.onJobAddedToTask(task); task.insertJob(newJob); } else { final ActorJob job = new ActorJob(); job.setRunnable(runnable); job.onJobAddedToTask(task); task.submit(job); } } public boolean isClosing() { ensureCalledFromWithinActor("isClosing()"); return task.isClosing(); } public boolean isClosed() { // for that lifecycle phase needs to be volatile final ActorLifecyclePhase lifecyclePhase = task.getLifecyclePhase(); return !(lifecyclePhase == ActorLifecyclePhase.STARTING || lifecyclePhase == ActorLifecyclePhase.STARTED); } public ActorLifecyclePhase getLifecyclePhase() { ensureCalledFromWithinActor("getLifecyclePhase()"); return task.getLifecyclePhase(); } public boolean isCalledFromWithinActor(final ActorJob job) { return job != null && job.getActor() == actor; } private ActorJob ensureCalledFromWithinActor(final String methodName) { final ActorJob currentJob = ensureCalledFromActorThread(methodName).getCurrentJob(); if (!isCalledFromWithinActor(currentJob)) { throw new UnsupportedOperationException( "Incorrect usage of actor." + methodName + ": must only be called from within the actor itself."); } return currentJob; } /** Mark actor as failed. This sets the lifecycle phase to 'FAILED' and discards all jobs. */ public void fail(final Throwable error) { ensureCalledFromWithinActor("fail()"); task.fail(error); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy