/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.ovsdb.southbound.transactions.md; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction; import org.opendaylight.controller.md.sal.common.api.data.TransactionChain; import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TransactionInvokerImpl implements TransactionInvoker,TransactionChainListener, Runnable, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(TransactionInvokerImpl.class); private static final int QUEUE_SIZE = 10000; private BindingTransactionChain chain; private DataBroker db; private BlockingQueue<TransactionCommand> inputQueue = new LinkedBlockingQueue<>(QUEUE_SIZE); private BlockingQueue<ReadWriteTransaction> successfulTransactionQueue = new LinkedBlockingQueue<>(QUEUE_SIZE); private BlockingQueue<AsyncTransaction<?, ?>> failedTransactionQueue = new LinkedBlockingQueue<>(QUEUE_SIZE); private ExecutorService executor; private Map<ReadWriteTransaction,TransactionCommand> transactionToCommand = new HashMap<>(); private List<ReadWriteTransaction> pendingTransactions = new ArrayList<>(); private final AtomicBoolean runTask = new AtomicBoolean(true); public TransactionInvokerImpl(DataBroker db) { this.db = db; this.chain = db.createTransactionChain(this); ThreadFactory threadFact = new ThreadFactoryBuilder().setNameFormat("transaction-invoker-impl-%d").build(); executor = Executors.newSingleThreadExecutor(threadFact); executor.submit(this); } @Override public void invoke(final TransactionCommand command) { // TODO what do we do if queue is full? inputQueue.offer(command); } @Override public void onTransactionChainFailed(TransactionChain<?, ?> chainArg, AsyncTransaction<?, ?> transaction, Throwable cause) { failedTransactionQueue.offer(transaction); } @Override public void onTransactionChainSuccessful(TransactionChain<?, ?> chainArg) { // NO OP } @Override public void run() { while (runTask.get()) { forgetSuccessfulTransactions(); List<TransactionCommand> commands = null; try { commands = extractCommands(); } catch (InterruptedException e) { LOG.warn("Extracting commands was interrupted.", e); continue; } ReadWriteTransaction transactionInFlight = null; try { for (TransactionCommand command: commands) { final ReadWriteTransaction transaction = chain.newReadWriteTransaction(); transactionInFlight = transaction; recordPendingTransaction(command, transaction); command.execute(transaction); Futures.addCallback(transaction.submit(), new FutureCallback<Void>() { @Override public void onSuccess(final Void result) { successfulTransactionQueue.offer(transaction); } @Override public void onFailure(final Throwable throwable) { // NOOP - handled by failure of transaction chain } }); } } catch (IllegalStateException e) { if (transactionInFlight != null) { // TODO: This method should distinguish exceptions on which the command should be // retried from exceptions on which the command should NOT be retried. // Then it should retry only the commands which should be retried, otherwise // this method will retry commands which will never be successful forever. failedTransactionQueue.offer(transactionInFlight); } LOG.warn("Failed to process an update notification from OVS.", e); } } } private List<TransactionCommand> extractResubmitCommands() { AsyncTransaction<?, ?> transaction = failedTransactionQueue.poll(); List<TransactionCommand> commands = new ArrayList<>(); if (transaction != null) { int index = pendingTransactions.lastIndexOf(transaction); List<ReadWriteTransaction> transactions = pendingTransactions.subList(index, pendingTransactions.size() - 1); for (ReadWriteTransaction tx: transactions) { commands.add(transactionToCommand.get(tx)); } resetTransactionQueue(); } return commands; } private void resetTransactionQueue() { chain.close(); chain = db.createTransactionChain(this); pendingTransactions = new ArrayList<>(); transactionToCommand = new HashMap<>(); failedTransactionQueue.clear(); successfulTransactionQueue.clear(); } private void recordPendingTransaction(TransactionCommand command, final ReadWriteTransaction transaction) { transactionToCommand.put(transaction, command); pendingTransactions.add(transaction); } private List<TransactionCommand> extractCommands() throws InterruptedException { List<TransactionCommand> commands = extractResubmitCommands(); commands.addAll(extractCommandsFromQueue()); return commands; } private List<TransactionCommand> extractCommandsFromQueue() throws InterruptedException { List<TransactionCommand> result = new ArrayList<>(); TransactionCommand command = inputQueue.take(); while (command != null) { result.add(command); command = inputQueue.poll(); } return result; } private void forgetSuccessfulTransactions() { ReadWriteTransaction transaction = successfulTransactionQueue.poll(); while (transaction != null) { pendingTransactions.remove(transaction); transactionToCommand.remove(transaction); transaction = successfulTransactionQueue.poll(); } } @Override public void close() throws Exception { this.executor.shutdown(); if (!this.executor.awaitTermination(1, TimeUnit.SECONDS)) { runTask.set(false); this.executor.shutdownNow(); } } }