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

com.turbospaces.ebean.JGroupsBroadcastChannel Maven / Gradle / Ivy

There is a newer version: 2.0.33
Show newest version
package com.turbospaces.ebean;

import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.commons.lang3.time.StopWatch;
import org.jgroups.blocks.MethodCall;
import org.jgroups.blocks.RequestOptions;
import org.jgroups.blocks.RpcDispatcher;
import org.jgroups.util.RspList;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.MoreExecutors;
import com.turbospaces.cfg.ApplicationProperties;
import com.turbospaces.common.PlatformUtil;
import com.turbospaces.executor.DefaultPlatformExecutorService;

import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheType;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.cache.CachedManyIds;
import io.micrometer.core.instrument.MeterRegistry;
import io.vavr.CheckedFunction0;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@SuppressWarnings("serial")
public class JGroupsBroadcastChannel implements BroadcastChannel, InitializingBean, DisposableBean {
    private final RpcDispatcher dispatcher;
    private final DefaultPlatformExecutorService platform;

    public JGroupsBroadcastChannel(ApplicationProperties props, MeterRegistry meterRegistry, RpcDispatcher dispatcher) {
        this.dispatcher = Objects.requireNonNull(dispatcher);
        this.platform = new DefaultPlatformExecutorService(props, meterRegistry, "jgroups-rpc-dispatcher", props.APP_PLATFORM_POOL_SIZE.get());
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        platform.afterPropertiesSet();
    }
    @Override
    public void destroy() throws Exception {
        platform.destroy();
    }
    @Override
    public FluentFuture>> broadcastPutAsync(String cacheKey, ServerCacheType type, String key, Object obj) {
        //
        // ~ fast and reusable output stream
        //
        UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get();

        //
        // ~ based on cache type
        //
        byte[] kser = PlatformUtil.serialize(out, key);
        byte[] vser = switch (type) {
            case NATURAL_KEY -> PlatformUtil.serialize(out, Serializable.class.cast(obj));
            case BEAN -> PlatformUtil.serialize(out, CachedBeanData.class.cast(obj));
            case COLLECTION_IDS -> PlatformUtil.serialize(out, CachedManyIds.class.cast(obj));
            default -> throw new IllegalArgumentException("Unexpected cache type: " + type);
        };

        //
        // ~ GC as fast as possible
        //
        out.reset();

        StopWatch stopWatch = StopWatch.createStarted();
        FluentFuture>> fluent = FluentFuture.from(platform.submit(new CheckedFunction0<>() {
            @Override
            public CompletableFuture> apply() throws Throwable {
                CompletableFuture> future = CompletableFuture.completedFuture(null);
                if (dispatcher.getChannel().isOpen()) {
                    MethodCall call = new MethodCall(JGroupsCacheManager.METHOD_ON_CACHE_PUT, cacheKey, kser, vser);
                    future = dispatcher.callRemoteMethodsWithFuture(null, call, RequestOptions.ASYNC());
                    stopWatch.stop();
                }
                return future;
            }
        }));
        fluent.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(Object result) {
                log.trace("putted {} entry on remote nodes by key: {} value: {} in: {}", cacheKey, key, obj, stopWatch);
                long time = stopWatch.getTime(TimeUnit.SECONDS);
                if (time > 0) {
                    log.error("put operation took too long: {}", stopWatch);
                }
            }
            @Override
            public void onFailure(Throwable t) {
                log.error(t.getMessage(), t);
            }
        }, MoreExecutors.directExecutor());

        return fluent;
    }
    @Override
    public FluentFuture>> broadcastRemoveAsync(String cacheKey, String key) {
        //
        // ~ fast and reusable output stream
        //
        UnsynchronizedByteArrayOutputStream out = UnsynchronizedByteArrayOutputStream.builder().get();
        byte[] kser = PlatformUtil.serialize(out, key);

        //
        // ~ GC as fast as possible
        //
        out.reset();

        StopWatch stopWatch = StopWatch.createStarted();
        FluentFuture>> fluent = FluentFuture.from(platform.submit(new CheckedFunction0<>() {
            @Override
            public CompletableFuture> apply() throws Throwable {
                CompletableFuture> future = CompletableFuture.completedFuture(null);
                if (dispatcher.getChannel().isOpen()) {
                    MethodCall call = new MethodCall(JGroupsCacheManager.METHOD_ON_CHANGE_REMOVE, cacheKey, kser);
                    future = dispatcher.callRemoteMethodsWithFuture(null, call, RequestOptions.ASYNC());
                    stopWatch.stop();
                }
                return future;
            }
        }));
        fluent.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(Object result) {
                log.trace("removed {} entry on remote nodes by key: {} in: {}", cacheKey, key, stopWatch);
                long time = stopWatch.getTime(TimeUnit.SECONDS);
                if (time > 0) {
                    log.error("remove operation took too long: {}", stopWatch);
                }
            }
            @Override
            public void onFailure(Throwable t) {
                log.error(t.getMessage(), t);
            }
        }, MoreExecutors.directExecutor());

        return fluent;
    }
    @Override
    public FluentFuture>> broadcastClearAllAsync(String cacheKey) {
        StopWatch stopWatch = StopWatch.createStarted();
        FluentFuture>> fluent = FluentFuture.from(platform.submit(new CheckedFunction0<>() {
            @Override
            public CompletableFuture> apply() throws Throwable {
                CompletableFuture> future = CompletableFuture.completedFuture(null);
                if (dispatcher.getChannel().isOpen()) {
                    MethodCall call = new MethodCall(JGroupsCacheManager.METHOD_ON_CACHE_CLEAR, cacheKey);
                    future = dispatcher.callRemoteMethodsWithFuture(null, call, RequestOptions.ASYNC());
                    stopWatch.stop();
                }
                return future;
            }
        }));
        fluent.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(Object result) {
                log.info("cleared {} on remote nodes in: {}", cacheKey, stopWatch);
                long time = stopWatch.getTime(TimeUnit.SECONDS);
                if (time > 0) {
                    log.error("clear operation took too long: {}", stopWatch);
                }
            }
            @Override
            public void onFailure(Throwable t) {
                log.error(t.getMessage(), t);
            }
        }, MoreExecutors.directExecutor());

        return fluent;
    }
    @Override
    public FluentFuture>> broadcastClearAllAsync() {
        StopWatch stopWatch = StopWatch.createStarted();
        FluentFuture>> fluent = FluentFuture.from(platform.submit(new CheckedFunction0<>() {
            @Override
            public CompletableFuture> apply() throws Throwable {
                CompletableFuture> future = CompletableFuture.completedFuture(null);
                if (dispatcher.getChannel().isOpen()) {
                    MethodCall call = new MethodCall(JGroupsCacheManager.METHOD_ON_CACHE_CLEAR_ALL, true);
                    future = dispatcher.callRemoteMethodsWithFuture(null, call, RequestOptions.ASYNC());
                    stopWatch.stop();
                }
                return future;
            }
        }));
        fluent.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(Object result) {
                long time = stopWatch.getTime(TimeUnit.SECONDS);
                if (time > 0) {
                    log.error("clearAll operation took too long: {}", stopWatch);
                }
            }
            @Override
            public void onFailure(Throwable t) {
                log.error(t.getMessage(), t);
            }
        }, MoreExecutors.directExecutor());

        return fluent;
    }
    @Override
    public FluentFuture>> broadcastAsync(ServerCacheNotification notification, String data) {
        StopWatch stopWatch = StopWatch.createStarted();
        FluentFuture>> fluent = FluentFuture.from(platform.submit(new CheckedFunction0<>() {
            @Override
            public CompletableFuture> apply() throws Throwable {
                CompletableFuture> future = CompletableFuture.completedFuture(null);
                if (dispatcher.getChannel().isOpen()) {
                    MethodCall call = new MethodCall(JGroupsCacheManager.METHOD_ON_MODIFIED, data);
                    future = dispatcher.callRemoteMethodsWithFuture(null, call, RequestOptions.ASYNC());
                    stopWatch.stop();
                }
                return future;
            }
        }));
        fluent.addCallback(new FutureCallback() {
            @Override
            public void onSuccess(Object result) {
                long time = stopWatch.getTime(TimeUnit.SECONDS);
                if (time > 0) {
                    log.error("notify operation took too long: {} for tables: {}", stopWatch, notification.getDependentTables());
                }
            }
            @Override
            public void onFailure(Throwable t) {
                log.error(t.getMessage(), t);
            }
        }, MoreExecutors.directExecutor());

        return fluent;
    }
}