/* * Copyright 2015 the original author or authors. * * 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 io.atomix.vertx; import java.io.File; import java.time.Duration; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import io.atomix.cluster.ClusterMembershipEvent; import io.atomix.cluster.ClusterMembershipEventListener; import io.atomix.core.Atomix; import io.atomix.core.lock.AtomicLock; import io.atomix.utils.concurrent.SingleThreadContext; import io.atomix.utils.concurrent.ThreadContext; import io.atomix.utils.serializer.Namespace; import io.atomix.utils.serializer.Namespaces; import io.atomix.utils.serializer.Serializer; import io.vertx.core.AsyncResult; import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.VertxException; import io.vertx.core.net.impl.ServerID; import io.vertx.core.shareddata.AsyncMap; import io.vertx.core.shareddata.Counter; import io.vertx.core.shareddata.Lock; import io.vertx.core.shareddata.impl.ClusterSerializable; import io.vertx.core.spi.cluster.AsyncMultiMap; import io.vertx.core.spi.cluster.ClusterManager; import io.vertx.core.spi.cluster.NodeListener; import static com.google.common.base.Preconditions.checkNotNull; /** * Vert.x Atomix cluster manager. * * @author <a href="http://github.com/kuujo">Jordan Halterman</a> */ public class AtomixClusterManager implements ClusterManager { private final Atomix atomix; private final ThreadContext context; private NodeListener listener; private final AtomicBoolean active = new AtomicBoolean(); private Vertx vertx; private final ClusterMembershipEventListener clusterListener = this::handleClusterEvent; private final LoadingCache<String, CompletableFuture<AtomicLock>> lockCache = CacheBuilder.newBuilder() .maximumSize(100) .build(new CacheLoader<String, CompletableFuture<AtomicLock>>() { @Override public CompletableFuture<AtomicLock> load(String key) throws Exception { return atomix.atomicLockBuilder(key).buildAsync(); } }); public AtomixClusterManager() { this(Atomix.builder(Atomix.config()).build()); } public AtomixClusterManager(String configFile) { this(new File(configFile)); } public AtomixClusterManager(File configFile) { this(new Atomix(configFile)); } public AtomixClusterManager(Atomix atomix) { this.atomix = checkNotNull(atomix, "atomix cannot be null"); this.context = new SingleThreadContext("atomix-vertx-%d"); } /** * Returns the underlying Atomix instance. * * @return The underlying Atomix instance. */ public Atomix atomix() { return atomix; } @Override public void setVertx(Vertx vertx) { this.vertx = vertx; } /** * Creates a new Vert.x compatible serializer. */ private Serializer createSerializer() { return Serializer.using(Namespace.builder() .setRegistrationRequired(false) .register(Namespaces.BASIC) .register(ServerID.class) .register(new ClusterSerializableSerializer<>(), ClusterSerializable.class) .build()); } @Override public <K, V> void getAsyncMultiMap(String name, Handler<AsyncResult<AsyncMultiMap<K, V>>> handler) { atomix.<K, V>atomicMultimapBuilder(name) .withSerializer(createSerializer()) .getAsync() .whenComplete(VertxFutures.convertHandler( handler, map -> new AtomixAsyncMultiMap<>(vertx, map.async()), vertx.getOrCreateContext())); } @Override public <K, V> void getAsyncMap(String name, Handler<AsyncResult<AsyncMap<K, V>>> handler) { atomix.<K, V>atomicMapBuilder(name) .withSerializer(createSerializer()) .buildAsync() .whenComplete(VertxFutures.convertHandler( handler, map -> new AtomixAsyncMap<>(vertx, map.async()), vertx.getOrCreateContext())); } @Override public <K, V> Map<K, V> getSyncMap(String name) { return new AtomixMap<>(vertx, atomix.<K, V>atomicMapBuilder(name) .withSerializer(createSerializer()) .build()); } @Override public void getLockWithTimeout(String name, long timeout, Handler<AsyncResult<Lock>> handler) { Context context = vertx.getOrCreateContext(); lockCache.getUnchecked(name).whenComplete((lock, error) -> { if (error == null) { lock.async().tryLock(Duration.ofMillis(timeout)).whenComplete((lockResult, lockError) -> { if (lockError == null) { if (lockResult.isPresent()) { context.runOnContext(v -> Future.<Lock>succeededFuture(new AtomixLock(vertx, lock)).setHandler(handler)); } else { context.runOnContext(v -> Future.<Lock>failedFuture(new VertxException("Timed out waiting to get lock " + name)).setHandler(handler)); } } else { context.runOnContext(v -> Future.<Lock>failedFuture(lockError).setHandler(handler)); } }); } else { context.runOnContext(v -> Future.<Lock>failedFuture(error).setHandler(handler)); } }); } @Override public void getCounter(String name, Handler<AsyncResult<Counter>> handler) { atomix.atomicCounterBuilder(name).buildAsync() .whenComplete(VertxFutures.convertHandler( handler, counter -> new AtomixCounter(vertx, counter.async()), vertx.getOrCreateContext())); } @Override public String getNodeID() { return atomix.getMembershipService().getLocalMember().id().id(); } @Override public List<String> getNodes() { return atomix.getMembershipService().getMembers() .stream() .map(member -> member.id().id()) .collect(Collectors.toList()); } @Override public void nodeListener(NodeListener nodeListener) { this.listener = nodeListener; } @Override public synchronized void join(Handler<AsyncResult<Void>> handler) { Context context = vertx.getOrCreateContext(); if (active.compareAndSet(false, true)) { atomix.start().whenComplete((result, error) -> { if (error == null) { atomix.getMembershipService().addListener(clusterListener); context.runOnContext(v -> Future.<Void>succeededFuture().setHandler(handler)); } else { context.runOnContext(v -> Future.<Void>failedFuture(error).setHandler(handler)); } }); } else { context.runOnContext(v -> Future.<Void>succeededFuture().setHandler(handler)); } } /** * Handles a cluster event. */ private void handleClusterEvent(ClusterMembershipEvent event) { NodeListener nodeListener = this.listener; if (nodeListener != null) { context.execute(() -> { if (active.get()) { switch (event.type()) { case MEMBER_ADDED: nodeListener.nodeAdded(event.subject().id().id()); break; case MEMBER_REMOVED: nodeListener.nodeLeft(event.subject().id().id()); break; default: break; } } }); } } @Override public synchronized void leave(Handler<AsyncResult<Void>> handler) { Context context = vertx.getOrCreateContext(); if (active.compareAndSet(true, false)) { atomix.stop().whenComplete((leaveResult, leaveError) -> { if (leaveError == null) { context.runOnContext(v -> Future.<Void>succeededFuture().setHandler(handler)); } else { context.runOnContext(v -> Future.<Void>failedFuture(leaveError).setHandler(handler)); } }); } else { context.runOnContext(v -> Future.<Void>succeededFuture().setHandler(handler)); } } @Override public boolean isActive() { return active.get(); } }