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

com.google.cloud.pubsub.v1.SequentialExecutorService Maven / Gradle / Ivy

There is a newer version: 1.134.1
Show newest version
/*
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.cloud.pubsub.v1;

import static com.google.common.util.concurrent.MoreExecutors.directExecutor;

import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutureCallback;
import com.google.api.core.ApiFutures;
import com.google.api.core.BetaApi;
import com.google.api.core.SettableApiFuture;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;

interface CancellableRunnable extends Runnable {
  void cancel(Throwable e);
}

/**
 * An executor service that runs the tasks with the same key sequentially. The tasks with the same
 * key will be run only when its predecessor has been completed while tasks with different keys can
 * be run in parallel.
 */
final class SequentialExecutorService {

  // This class is not directly usable.
  private SequentialExecutorService() {}

  /**
   * This Executor takes a serial stream of string keys and {@code Runnable} tasks, and runs the
   * tasks with the same key sequentially. Tasks with the same key will be run only when its
   * predecessor has been completed while tasks with different keys can be run in parallel.
   */
  private abstract static class SequentialExecutor {
    // Maps keys to tasks.
    protected final Map> tasksByKey;
    protected final Executor executor;

    private SequentialExecutor(Executor executor) {
      this.executor = executor;
      this.tasksByKey = new HashMap<>();
    }

    boolean hasTasksInflight(String key) {
      synchronized (tasksByKey) {
        return tasksByKey.containsKey(key);
      }
    }

    protected void execute(final String key, R task) {
      synchronized (tasksByKey) {
        Queue newTasks = tasksByKey.get(key);
        // If this key is already being handled, add it to the queue and return.
        if (newTasks != null) {
          newTasks.add(task);
          return;
        } else {
          newTasks = new LinkedList<>();
          newTasks.add(task);
          tasksByKey.put(key, newTasks);
        }
      }

      callNextTaskAsync(key);
    }

    protected void callNextTaskAsync(final String key) {
      boolean executeTask = true;
      synchronized (tasksByKey) {
        Queue tasks = tasksByKey.get(key);
        if (tasks != null && tasks.isEmpty()) {
          // Only remove the Queue after all tasks were completed
          tasksByKey.remove(key);
          executeTask = false;
        }
      }
      if (executeTask) {
        executor.execute(
            new Runnable() {
              @Override
              public void run() {
                R task = null;
                synchronized (tasksByKey) {
                  Queue tasks = tasksByKey.get(key);
                  if (tasks != null && !tasks.isEmpty()) {
                    task = tasks.poll();
                  }
                }
                if (task != null) {
                  task.run();
                }
              }
            });
      }
    }
  }

  @BetaApi
  static class AutoExecutor extends SequentialExecutor {
    AutoExecutor(Executor executor) {
      super(executor);
    }

    /** Runs synchronous {@code Runnable} tasks sequentially. */
    void submit(final String key, final Runnable task) {
      super.execute(
          key,
          new Runnable() {
            @Override
            public void run() {
              try {
                task.run();
              } finally {
                callNextTaskAsync(key);
              }
            }
          });
    }
  }

  /**
   * Runs asynchronous {@code Callable} tasks sequentially for the same key. If one of the tasks
   * fails, other tasks with the same key that have not been executed will be cancelled.
   */
  @BetaApi
  static class CallbackExecutor extends SequentialExecutor {
    static CancellationException CANCELLATION_EXCEPTION =
        new CancellationException(
            "Execution cancelled because executing previous runnable failed.");

    private final Set keysWithErrors = Collections.synchronizedSet(new HashSet());

    CallbackExecutor(Executor executor) {
      super(executor);
    }

    /**
     * Runs asynchronous {@code Callable} tasks sequentially. If one of the tasks fails, other tasks
     * with the same key that have not been executed will be cancelled.
     *
     * 

This method does the following in a chain: * *

    *
  1. Creates an `ApiFuture` that can be used for tracking progress. *
  2. Creates a `CancellableRunnable` out of the `Callable` *
  3. Adds the `CancellableRunnable` to the task queue *
  4. Once the task is ready to be run, it will execute the `Callable` *
  5. When the `Callable` completes one of two things happens: *
      *
    1. On success: *
        *
      1. Complete the `ApiFuture` by setting the return value. *
      2. Call the next task. *
      *
    2. On Failure: *
        *
      1. Fail the `ApiFuture` by setting the exception. *
      2. Cancel all tasks in the queue. *
      *
    *
* * @param key The key for the task queue * @param callable The thing to run * @param The return type for the `Callable`'s `ApiFuture`. * @return an `ApiFuture` for tracking. */ ApiFuture submit(final String key, final Callable> callable) { // Step 1: create a future for the user final SettableApiFuture future = SettableApiFuture.create(); if (keysWithErrors.contains(key)) { future.setException(CANCELLATION_EXCEPTION); return future; } // Step 2: create the CancellableRunnable // Step 3: add the task to queue via `execute` CancellableRunnable task = new CancellableRunnable() { private boolean cancelled = false; @Override public void run() { // the task was cancelled if (cancelled) { return; } try { // Step 4: call the `Callable` ApiFutureCallback callback = new ApiFutureCallback() { // Step 5.1: on success @Override public void onSuccess(T msg) { callNextTaskAsync(key); future.set(msg); } // Step 5.2: on failure @Override public void onFailure(Throwable e) { future.setException(e); cancelQueuedTasks(key, CANCELLATION_EXCEPTION); } }; ApiFutures.addCallback(callable.call(), callback, directExecutor()); } catch (Exception e) { cancel(e); } } @Override public void cancel(Throwable e) { this.cancelled = true; future.setException(e); } }; execute(key, task); return future; } boolean keyHasError(String key) { return keysWithErrors.contains(key); } void resumePublish(String key) { keysWithErrors.remove(key); } void stopPublish(String key) { keysWithErrors.add(key); } /** Cancels every task in the queue associated with {@code key}. */ private void cancelQueuedTasks(final String key, Throwable e) { keysWithErrors.add(key); synchronized (tasksByKey) { final Queue tasks = tasksByKey.get(key); if (tasks != null) { while (!tasks.isEmpty()) { tasks.poll().cancel(e); } tasksByKey.remove(key); } } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy