io.pravega.test.integration.selftest.Producer.java Source code

Java tutorial

Introduction

Here is the source code for io.pravega.test.integration.selftest.Producer.java

Source

/**
 * Copyright (c) 2017 Dell Inc., or its subsidiaries. All Rights Reserved.
 *
 * 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */
package io.pravega.test.integration.selftest;

import io.pravega.common.AbstractTimer;
import io.pravega.common.Exceptions;
import io.pravega.common.concurrent.Futures;
import io.pravega.test.integration.selftest.adapters.StoreAdapter;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
import lombok.SneakyThrows;
import lombok.val;
import org.apache.commons.lang.NotImplementedException;

/**
 * Represents an Operation Producer for the Self Tester.
 */
class Producer extends Actor {
    //region Members

    private static final Supplier<Long> TIME_PROVIDER = System::nanoTime;
    private final String logId;
    private final AtomicInteger iterationCount;
    private final AtomicBoolean canContinue;
    private final int id;

    //endregion

    //region Constructor

    /**
     * Creates a new instance of the Producer class.
     *
     * @param id         The Id of the producer.
     * @param config     Test Configuration.
     * @param dataSource DataSource for the Producer.
     * @param store      A StoreAdapter to execute operations on.
     * @param executor   An Executor to use for async operations.
     */
    Producer(int id, TestConfig config, ProducerDataSource dataSource, StoreAdapter store,
            ScheduledExecutorService executor) {
        super(config, dataSource, store, executor);

        this.id = id;
        this.logId = String.format("Producer[%s]", id);
        this.iterationCount = new AtomicInteger();
        this.canContinue = new AtomicBoolean(true);
    }

    //endregion

    //region Actor Implementation

    @Override
    protected CompletableFuture<Void> run() {
        this.canContinue.set(true);
        return Futures.loop(this::canLoop, this::runOneIteration, this.executorService);
    }

    @Override
    protected String getLogId() {
        return this.logId;
    }

    //endregion

    //region Producer Implementation

    /**
     * Executes one iteration of the Producer.
     * 1. Requests a new ProducerOperation from the DataSource.
     * 2. Executes it.
     * 3. Completes the ProducerOperation with either success or failure based on the outcome step #2.
     */
    private CompletableFuture<Void> runOneIteration() {
        this.iterationCount.incrementAndGet();

        val futures = new ArrayList<CompletableFuture<Void>>();
        for (int i = 0; i < this.config.getProducerParallelism(); i++) {
            ProducerOperation op = this.dataSource.nextOperation();
            if (op == null) {
                // Nothing more to do.
                this.canContinue.set(false);
                break;
            }

            CompletableFuture<Void> result;
            try {
                CompletableFuture<Void> waitOn = op.getWaitOn();
                if (waitOn != null) {
                    result = waitOn.exceptionally(ex -> null).thenComposeAsync(v -> executeOperation(op),
                            this.executorService);
                } else {
                    result = executeOperation(op);
                }
            } catch (Throwable ex) {
                // Catch and handle sync errors.
                op.completed(-1);
                if (handleOperationError(ex, op)) {
                    // Exception handled; skip this iteration since there's nothing more we can do.
                    continue;
                } else {
                    result = Futures.failedFuture(ex);
                }
            }

            futures.add(result.exceptionally(ex -> {
                // Catch and handle async errors.
                if (handleOperationError(ex, op)) {
                    return null;
                }

                throw new CompletionException(ex);
            }));
        }

        return Futures.allOf(futures);
    }

    private boolean handleOperationError(Throwable ex, ProducerOperation op) {
        // Log & throw every exception.
        ex = Exceptions.unwrap(ex);
        if (ex instanceof ProducerDataSource.UnknownStreamException) {
            // This is OK: some other producer deleted the segment after we requested the operation and until we
            // tried to apply it.
            return true;
        } else if (ex instanceof NotImplementedException || ex instanceof UnsupportedOperationException) {
            // Operation is not supported. No need to fail the test; just log it.
            TestLogger.log(getLogId(), "Operation '%s' cannot be executed on '%s' because it is not supported.",
                    op.getType(), op.getTarget());
            return true;
        }

        TestLogger.log(getLogId(), "Iteration %s FAILED for Op '%s' with %s.", this.iterationCount, op, ex);
        this.canContinue.set(false);
        op.failed(ex);
        return false;
    }

    /**
     * Determines whether the Producer can loop to another iteration.
     */
    private boolean canLoop() {
        return isRunning() && this.canContinue.get();
    }

    /**
     * Executes the given operation.
     */
    private CompletableFuture<Void> executeOperation(ProducerOperation operation) {
        CompletableFuture<Void> result;
        final AtomicLong startTime = new AtomicLong(TIME_PROVIDER.get());
        if (operation.getType() == ProducerOperationType.CREATE_TRANSACTION) {
            // Create the Transaction, then record it's name in the operation's result.
            StoreAdapter.Feature.Transaction.ensureSupported(this.store, "create transaction");
            startTime.set(TIME_PROVIDER.get());
            result = this.store.createTransaction(operation.getTarget(), this.config.getTimeout())
                    .thenAccept(operation::setResult);
        } else if (operation.getType() == ProducerOperationType.MERGE_TRANSACTION) {
            // Merge the Transaction.
            StoreAdapter.Feature.Transaction.ensureSupported(this.store, "merge transaction");
            startTime.set(TIME_PROVIDER.get());
            result = this.store.mergeTransaction(operation.getTarget(), this.config.getTimeout());
        } else if (operation.getType() == ProducerOperationType.ABORT_TRANSACTION) {
            // Abort the Transaction.
            StoreAdapter.Feature.Transaction.ensureSupported(this.store, "abort transaction");
            startTime.set(TIME_PROVIDER.get());
            result = this.store.abortTransaction(operation.getTarget(), this.config.getTimeout());
        } else if (operation.getType() == ProducerOperationType.APPEND) {
            // Generate some random data, then append it.
            StoreAdapter.Feature.Append.ensureSupported(this.store, "append");
            Event event = this.dataSource.nextEvent(operation.getTarget(), this.id);
            operation.setLength(event.getSerialization().getLength());
            startTime.set(TIME_PROVIDER.get());
            result = this.store.append(operation.getTarget(), event, this.config.getTimeout());
        } else if (operation.getType() == ProducerOperationType.SEAL) {
            // Seal the target.
            StoreAdapter.Feature.Seal.ensureSupported(this.store, "seal");
            startTime.set(TIME_PROVIDER.get());
            result = this.store.seal(operation.getTarget(), this.config.getTimeout());
        } else {
            throw new IllegalArgumentException("Unsupported Operation Type: " + operation.getType());
        }

        return result.exceptionally(ex -> attemptReconcile(ex, operation)).thenRun(
                () -> operation.completed((TIME_PROVIDER.get() - startTime.get()) / AbstractTimer.NANOS_TO_MILLIS));
    }

    @SneakyThrows
    private Void attemptReconcile(Throwable ex, ProducerOperation operation) {
        ex = Exceptions.unwrap(ex);
        if (this.dataSource.isClosed(operation.getTarget())) {
            return null;
        } else {
            throw ex;
        }
    }

    //endregion
}