/*
* 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);
}
});
}
}