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

com.proofpoint.discovery.client.ServiceDescriptorsUpdater Maven / Gradle / Ivy

There is a newer version: 3.23
Show newest version
/*
 * Copyright 2010 Proofpoint, Inc.
 *
 * 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
 *
 * 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.proofpoint.discovery.client;

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 com.proofpoint.log.Logger;
import com.proofpoint.node.NodeInfo;
import com.proofpoint.units.Duration;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.PostConstruct;

import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.google.common.base.Preconditions.checkState;
import static com.proofpoint.discovery.client.announce.DiscoveryAnnouncementClient.DEFAULT_DELAY;
import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElse;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

public final class ServiceDescriptorsUpdater
{
    private static final Logger log = Logger.get(ServiceDescriptorsUpdater.class);

    private final ServiceDescriptorsListener target;
    private final String type;
    private final String pool;
    private final DiscoveryLookupClient discoveryClient;
    private final AtomicReference serviceDescriptors = new AtomicReference<>();
    private final ScheduledExecutorService executor;

    private final AtomicBoolean started = new AtomicBoolean(false);
    private final ExponentialBackOff errorBackOff;

    public ServiceDescriptorsUpdater(ServiceDescriptorsListener target, String type, ServiceSelectorConfig selectorConfig, NodeInfo nodeInfo, DiscoveryLookupClient discoveryClient, ScheduledExecutorService executor)
    {
        requireNonNull(target, "target is null");
        requireNonNull(type, "type is null");
        requireNonNull(selectorConfig, "selectorConfig is null");
        requireNonNull(nodeInfo, "nodeInfo is null");
        requireNonNull(discoveryClient, "discoveryClient is null");
        requireNonNull(executor, "executor is null");

        this.target = target;
        this.type = type;
        this.pool = requireNonNullElse(selectorConfig.getPool(), nodeInfo.getPool());
        this.discoveryClient = discoveryClient;
        this.executor = executor;
        this.errorBackOff = new ExponentialBackOff(
                new Duration(1, MILLISECONDS),
                new Duration(1, SECONDS),
                String.format("Discovery server connect succeeded for refresh (%s/%s)", type, pool),
                String.format("Cannot connect to discovery server for refresh (%s/%s)", type, pool),
                log);
    }

    @PostConstruct
    public void start()
    {
        if (started.compareAndSet(false, true)) {
            checkState(!executor.isShutdown(), "CachingServiceSelector has been destroyed");

            // if discovery is available, get the initial set of servers before starting
            try {
                refresh().get(1, TimeUnit.SECONDS);
            }
            catch (Exception ignored) {
            }
        }
    }

    private ListenableFuture refresh()
    {
        final ServiceDescriptors oldDescriptors = this.serviceDescriptors.get();

        final ListenableFuture future;
        if (oldDescriptors == null) {
            future = discoveryClient.getServices(type, pool);
        }
        else {
            future = discoveryClient.refreshServices(oldDescriptors);
        }

        return chainedCallback(future, new FutureCallback()
        {
            @Override
            @SuppressFBWarnings("NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE")
            public void onSuccess(ServiceDescriptors newDescriptors)
            {
                Duration delay = null;

                if (newDescriptors.getServiceDescriptors().isEmpty()) {
                    // If no new service descriptors are available, log a warning.
                    // Keep any previous service descriptors to provide some robustness for degraded operation.
                    if (serviceDescriptors.get() == null) {
                        log.warn("Discovery returned zero available service instances for service %s, pool %s. No " +
                                "previous service descriptors are available.",
                                newDescriptors.getType(), newDescriptors.getPool());
                    }
                    else {
                        log.warn("Discovery returned zero available service instances for service %s, pool %s. Keeping " +
                                "previous set of instances.", newDescriptors.getType(), newDescriptors.getPool());
                    }
                }
                else {
                    // Update with the new descriptors.
                    serviceDescriptors.set(newDescriptors);
                    target.updateServiceDescriptors(newDescriptors.getServiceDescriptors());
                    delay = newDescriptors.getMaxAge();
                }

                errorBackOff.success();

                if (delay == null) {
                    delay = DEFAULT_DELAY;
                }
                scheduleRefresh(delay);
            }

            @Override
            public void onFailure(Throwable t)
            {
                Duration duration = errorBackOff.failed(t);
                scheduleRefresh(duration);
            }
        }, executor);
    }

    private void scheduleRefresh(Duration delay)
    {
        // already stopped?  avoids rejection exception
        if (executor.isShutdown()) {
            return;
        }
        executor.schedule((Runnable) this::refresh, delay.toMillis(), TimeUnit.MILLISECONDS);
    }

    private static  ListenableFuture chainedCallback(
            ListenableFuture future,
            final FutureCallback callback,
            Executor executor)
    {
        final SettableFuture done = SettableFuture.create();
        Futures.addCallback(future, new FutureCallback()
        {
            @Override
            public void onSuccess(V result)
            {
                try {
                    callback.onSuccess(result);
                }
                finally {
                    done.set(result);
                }
            }

            @Override
            public void onFailure(Throwable t)
            {
                try {
                    callback.onFailure(t);
                }
                finally {
                    done.setException(t);
                }
            }
        }, executor);
        return done;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy