org.dcache.poolmanager.PoolManagerHandlerSubscriber.java Source code

Java tutorial

Introduction

Here is the source code for org.dcache.poolmanager.PoolManagerHandlerSubscriber.java

Source

/*
 * dCache - http://www.dcache.org/
 *
 * Copyright (C) 2016 Deutsches Elektronen-Synchrotron
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.dcache.poolmanager;

import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.concurrent.GuardedBy;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;

import diskCacheV111.util.CacheException;
import diskCacheV111.util.TimeoutCacheException;
import diskCacheV111.vehicles.Message;
import diskCacheV111.vehicles.PoolIoFileMessage;
import diskCacheV111.vehicles.PoolManagerMessage;

import dmg.cells.nucleus.CellAddressCore;
import dmg.cells.nucleus.CellEndpoint;
import dmg.cells.nucleus.CellLifeCycleAware;
import dmg.cells.nucleus.CellMessage;

import org.dcache.cells.CellStub;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.util.concurrent.Futures.*;
import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly;
import static dmg.cells.nucleus.CellEndpoint.SendFlag.RETRY_ON_NO_ROUTE_TO_CELL;

/**
 * Client stub for obtaining implementations of PoolManagerHandler.
 *
 * <p>This class allows an implementation of PoolManagerHandler to be obtained from a
 * pool manager asynchronously. Updates to this implementation are fetched
 * transparently and asynchronously until the component is stopped.
 *
 * <p>The class implements PoolManagerHandler and passes along calls to the obtained
 * PoolManagerHandler.
 */
public class PoolManagerHandlerSubscriber implements CellLifeCycleAware, PoolManagerHandler {
    private final static Logger LOGGER = LoggerFactory.getLogger(PoolManagerHandlerSubscriber.class);

    /**
     * How frequently to poll for updates. Usually updates are propagated immediately by
     * downstream as a result of a PoolManagerGetUpdatedHandler request, but in case
     * such a request is lost, it will at most be until the poll period expires before
     * we resubmit the request.
     */
    private static final int POLLING_PERIOD = 60000;

    private CellStub poolManager;

    private boolean isStopped;

    private ListenableFuture<SerializablePoolManagerHandler> current;

    private SettableFuture<Void> startGate = SettableFuture.create();

    /**
     * Sets the cell stub used to query the PoolManagerHandler.
     * @param poolManager
     */
    @Required
    public void setPoolManager(CellStub poolManager) {
        this.poolManager = poolManager;
    }

    @PostConstruct
    public synchronized void start() {
        current = transformAsync(startGate,
                ignored -> CellStub.transform(query(new PoolMgrGetHandler()), PoolMgrGetHandler::getHandler));

        Futures.addCallback(current, new FutureCallback<SerializablePoolManagerHandler>() {
            @Override
            public void onSuccess(SerializablePoolManagerHandler handler) {
                synchronized (PoolManagerHandlerSubscriber.this) {
                    try {
                        current = Futures.immediateFuture(handler);
                        if (!isStopped) {
                            ListenableFuture<SerializablePoolManagerHandler> next = CellStub.transform(
                                    query(new PoolMgrGetUpdatedHandler(handler.getVersion())),
                                    PoolMgrGetHandler::getHandler);
                            Futures.addCallback(next, this);
                        }
                    } catch (Throwable t) {
                        current = Futures.immediateFailedFuture(t);
                        LOGGER.error(
                                "Failure in pool manager handler subscriber. Please report to support@dcache.org.",
                                t);
                        throw t;
                    }
                }
            }

            @Override
            public void onFailure(Throwable t) {
                synchronized (PoolManagerHandlerSubscriber.this) {
                    current = Futures.immediateFailedFuture(t);
                }
            }
        });
    }

    @Override
    public void afterStart() {
        startGate.set(null);
        startGate = null;
    }

    @Override
    public synchronized void beforeStop() {
        isStopped = true;
        if (current != null) {
            current.cancel(false);
        }
    }

    @GuardedBy("this")
    private ListenableFuture<PoolMgrGetHandler> query(PoolMgrGetHandler request) {
        return catchingAsync(poolManager.send(request, POLLING_PERIOD, RETRY_ON_NO_ROUTE_TO_CELL),
                TimeoutCacheException.class, t -> retryQuery(request));

    }

    @GuardedBy("this")
    private ListenableFuture<PoolMgrGetHandler> retryQuery(PoolMgrGetHandler request) {
        if (isStopped) {
            throw new CancellationException("Subscriber was stopped.");
        }
        return query(request);
    }

    /**
     * Returns the currently cached implementation of PoolManagerHandler.
     *
     * <p>This is returned as a {@link ListenableFuture} since during startup no handler
     * may be available. In such case the future is not done and calling {@code get}
     * on it will block.
     *
     * @return A future cached PoolManagerHandler implementation.
     */
    public synchronized ListenableFuture<SerializablePoolManagerHandler> current() {
        checkState(current != null);
        return nonCancellationPropagating(current);
    }

    @Override
    public <T extends PoolIoFileMessage> ListenableFuture<T> startAsync(CellEndpoint endpoint,
            CellAddressCore address, T msg, long timeout) {
        return withCurrent(handler -> handler.startAsync(endpoint, address, msg, timeout));
    }

    @Override
    public void start(CellEndpoint endpoint, CellMessage envelope, PoolIoFileMessage msg) {
        withCurrent(handler -> handler.start(endpoint, envelope, msg), endpoint, envelope, msg);
    }

    @Override
    public <T extends PoolManagerMessage> ListenableFuture<T> sendAsync(CellEndpoint endpoint, T msg,
            long timeout) {
        return withCurrent(handler -> handler.sendAsync(endpoint, msg, timeout));
    }

    @Override
    public void send(CellEndpoint endpoint, CellMessage envelope, PoolManagerMessage msg) {
        withCurrent(handler -> handler.send(endpoint, envelope, msg), endpoint, envelope, msg);
    }

    @Override
    public String toString() {
        ListenableFuture<SerializablePoolManagerHandler> current = this.current;
        if (current.isDone()) {
            try {
                return getUninterruptibly(current).toString();
            } catch (ExecutionException e) {
                return e.getCause().toString();
            }
        }
        return "unavailable";
    }

    private <T extends Message> ListenableFuture<T> withCurrent(
            AsyncFunction<SerializablePoolManagerHandler, T> f) {
        return transformAsync(current(), f);
    }

    private void withCurrent(Consumer<SerializablePoolManagerHandler> f, CellEndpoint endpoint,
            CellMessage envelope, Message msg) {
        Futures.addCallback(current, new FutureCallback<SerializablePoolManagerHandler>() {
            @Override
            public void onSuccess(@Nullable SerializablePoolManagerHandler handler) {
                f.accept(handler);
            }

            @Override
            public void onFailure(Throwable t) {
                msg.setFailed(CacheException.UNEXPECTED_SYSTEM_EXCEPTION, t);
                envelope.setMessageObject(msg);
                envelope.revertDirection();
                endpoint.sendMessage(envelope);
            }
        });
    }
}