Fix Connectables connecting in the wrong order
This commit is contained in:
parent
8863e72fba
commit
34b9d4f246
|
@ -1,12 +0,0 @@
|
|||
package tc.oc.api.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
/**
|
||||
* Generic annotation for something that requires an API connection,
|
||||
* and should be ommitted or cause an error if not connected.
|
||||
*
|
||||
* (maybe we should make that distinction explicit?)
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ApiRequired {}
|
|
@ -2,16 +2,21 @@ package tc.oc.api.connectable;
|
|||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.inject.binder.ScopedBindingBuilder;
|
||||
import tc.oc.minecraft.api.event.Activatable;
|
||||
import tc.oc.commons.core.plugin.PluginFacet;
|
||||
|
||||
/**
|
||||
* Service that needs to be connected and disconnected along with the API.
|
||||
* Service that needs to be connected and disconnected along with the API
|
||||
*
|
||||
* Use a {@link ConnectableBinder} to register these.
|
||||
* Registration happens automatically the first time any {@link Connectable}
|
||||
* instance is provisioned through Guice. If this happens before or during
|
||||
* the connection process, the instance will be connected in the same order
|
||||
* that it was provisioned, with respect to other {@link Connectable}s.
|
||||
*
|
||||
* TODO: This should probably extend {@link PluginFacet},
|
||||
* but to do that, API needs to be able to find the services bound in other plugins.
|
||||
* If a new {@link Connectable} instance is provisioned after the connection
|
||||
* phase is complete, an exception is thrown. To ensure that a {@link Connectable}
|
||||
* is provisioned in time to be connected, it is usually scoped with
|
||||
* {@link ScopedBindingBuilder#asEagerSingleton()}
|
||||
*/
|
||||
public interface Connectable extends Activatable {
|
||||
default void connect() throws IOException {};
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package tc.oc.api.connectable;
|
||||
|
||||
import com.google.inject.Binder;
|
||||
import tc.oc.commons.core.inject.SetBinder;
|
||||
|
||||
public class ConnectableBinder extends SetBinder<Connectable> {
|
||||
public ConnectableBinder(Binder binder) {
|
||||
super(binder);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,21 @@
|
|||
package tc.oc.api.connectable;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import tc.oc.commons.core.inject.HybridManifest;
|
||||
import tc.oc.commons.core.plugin.PluginFacetBinder;
|
||||
import tc.oc.minecraft.api.event.ListenerBinder;
|
||||
|
||||
public class ConnectablesManifest extends HybridManifest {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bindAndExpose(Connector.class);
|
||||
new PluginFacetBinder(binder())
|
||||
.add(Connector.class);
|
||||
bind(Connector.class);
|
||||
new ListenerBinder(binder())
|
||||
.bindListener().to(Connector.class);
|
||||
|
||||
final Provider<Connector> connectorProvider = getProvider(Connector.class);
|
||||
publicBinder().bindProvisionSubtypesOfListener(Connectable.class, provision -> {
|
||||
connectorProvider.get().register(provision.provision());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package tc.oc.api.connectable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
@ -8,28 +12,36 @@ import javax.inject.Singleton;
|
|||
|
||||
import tc.oc.commons.core.exception.ExceptionHandler;
|
||||
import tc.oc.commons.core.logging.Loggers;
|
||||
import tc.oc.commons.core.plugin.PluginFacet;
|
||||
import tc.oc.commons.core.util.ExceptionUtils;
|
||||
import tc.oc.minecraft.api.event.Enableable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static tc.oc.commons.core.IterableUtils.reverseForEach;
|
||||
import static tc.oc.commons.core.exception.LambdaExceptionUtils.rethrowConsumer;
|
||||
|
||||
@Singleton
|
||||
public class Connector implements PluginFacet {
|
||||
class Connector implements Enableable {
|
||||
|
||||
protected final Logger logger;
|
||||
private final ExceptionHandler exceptionHandler;
|
||||
private final Set<Connectable> services;
|
||||
private boolean connected;
|
||||
private final Set<Connectable> registered = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
private final Deque<Connectable> pending = new LinkedList<>();
|
||||
private final Deque<Connectable> connected = new LinkedList<>();
|
||||
private boolean finishedConnecting;
|
||||
|
||||
@Inject
|
||||
Connector(Loggers loggers, ExceptionHandler exceptionHandler, Set<Connectable> services) {
|
||||
@Inject Connector(Loggers loggers, ExceptionHandler exceptionHandler) {
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
this.services = services;
|
||||
this.logger = loggers.get(getClass());
|
||||
}
|
||||
|
||||
void register(Connectable connectable) {
|
||||
if(registered.add(connectable)) {
|
||||
if(finishedConnecting) {
|
||||
throw new IllegalStateException("Tried to provision a " + Connectable.class.getSimpleName() +
|
||||
" when already connected");
|
||||
}
|
||||
pending.add(connectable);
|
||||
}
|
||||
}
|
||||
|
||||
private void connect(Connectable service) throws IOException {
|
||||
if(service.isActive()) {
|
||||
logger.fine(() -> "Connecting " + service.getClass().getName());
|
||||
|
@ -44,23 +56,25 @@ public class Connector implements PluginFacet {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
checkState(!connected, "already connected");
|
||||
connected = true;
|
||||
checkState(!finishedConnecting, "already connected");
|
||||
logger.fine(() -> "Connecting all services");
|
||||
ExceptionUtils.propagate(() -> services.forEach(rethrowConsumer(this::connect)));
|
||||
for(;;) {
|
||||
final Connectable connectable = pending.poll();
|
||||
if(connectable == null) break;
|
||||
ExceptionUtils.propagate(() -> connect(connectable));
|
||||
connected.push(connectable);
|
||||
}
|
||||
finishedConnecting = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
checkState(connected, "not connected");
|
||||
connected = false;
|
||||
checkState(finishedConnecting, "not connected");
|
||||
logger.fine(() -> "Disconnecting all services");
|
||||
reverseForEach(services, service -> exceptionHandler.run(() -> disconnect(service)));
|
||||
while(!connected.isEmpty()) {
|
||||
exceptionHandler.run(() -> disconnect(connected.pop()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.binder.LinkedBindingBuilder;
|
||||
|
@ -12,8 +11,8 @@ import com.google.inject.multibindings.Multibinder;
|
|||
import com.google.inject.multibindings.OptionalBinder;
|
||||
import tc.oc.api.docs.virtual.Model;
|
||||
import tc.oc.api.docs.virtual.PartialModel;
|
||||
import tc.oc.api.connectable.ConnectableBinder;
|
||||
import tc.oc.api.queue.QueueQueryService;
|
||||
import tc.oc.commons.core.inject.Binders;
|
||||
import tc.oc.commons.core.inject.KeyedManifest;
|
||||
import tc.oc.commons.core.inject.SingletonManifest;
|
||||
import tc.oc.commons.core.reflect.ResolvableType;
|
||||
|
@ -29,7 +28,7 @@ public class ModelBinder<M extends Model, P extends PartialModel> implements Mod
|
|||
|
||||
private final TypeLiteral<M> M;
|
||||
private final TypeLiteral<P> P;
|
||||
private final Binder binder;
|
||||
private final Binders binder;
|
||||
private final Multibinder<ModelMeta> metas;
|
||||
private final OptionalBinder<QueryService<M>> queryServiceBinder;
|
||||
private final OptionalBinder<UpdateService<P>> updateServiceBinder;
|
||||
|
@ -53,7 +52,7 @@ public class ModelBinder<M extends Model, P extends PartialModel> implements Mod
|
|||
}
|
||||
|
||||
private ModelBinder(ProtectedBinder protectedBinder, TypeLiteral<M> M, TypeLiteral<P> P) {
|
||||
this.binder = protectedBinder.publicBinder();
|
||||
this.binder = Binders.wrap(protectedBinder.publicBinder());
|
||||
this.M = M;
|
||||
this.P = P;
|
||||
|
||||
|
@ -68,7 +67,7 @@ public class ModelBinder<M extends Model, P extends PartialModel> implements Mod
|
|||
}
|
||||
|
||||
public LinkedBindingBuilder<ModelStore<M>> bindStore() {
|
||||
new ConnectableBinder(binder).addBinding().to(ModelStore(M));
|
||||
binder.provisionEagerly(ModelStore(M));
|
||||
new SuspendableBinder(binder).addBinding().to(ModelStore(M));
|
||||
return storeBinder.setBinding();
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ public class QueueClient implements Connectable {
|
|||
}
|
||||
|
||||
try {
|
||||
this.channel.basicPublish(exchange.name(),
|
||||
getChannel().basicPublish(exchange.name(),
|
||||
publish.routingKey(),
|
||||
publish.mandatory(),
|
||||
publish.immediate(),
|
||||
|
@ -220,16 +220,22 @@ public class QueueClient implements Connectable {
|
|||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
// create connection and channel
|
||||
logger.info("Connecting to AMQP API at " + Joiners.onCommaSpace.join(config.getAddresses()));
|
||||
this.connection = this.createConnectionFactory().newConnection(this.config.getAddresses().toArray(new Address[0]));
|
||||
this.channel = this.connection.createChannel();
|
||||
if(config.getAddresses().isEmpty()) {
|
||||
logger.warning("Skipping AMQP connection because no addresses are configured");
|
||||
} else {
|
||||
logger.info("Connecting to AMQP API at " + Joiners.onCommaSpace.join(config.getAddresses()));
|
||||
this.connection = this.createConnectionFactory().newConnection(this.config.getAddresses().toArray(new Address[0]));
|
||||
this.channel = this.connection.createChannel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() throws IOException {
|
||||
ExecutorUtils.shutdownImpatiently(executorService, logger, SHUTDOWN_TIMEOUT);
|
||||
this.channel.close();
|
||||
this.connection.close();
|
||||
|
||||
if(channel != null) {
|
||||
channel.close();
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package tc.oc.api.queue;
|
||||
|
||||
import tc.oc.api.connectable.ConnectableBinder;
|
||||
import tc.oc.api.message.MessageQueue;
|
||||
import tc.oc.commons.core.inject.HybridManifest;
|
||||
import tc.oc.minecraft.suspend.SuspendableBinder;
|
||||
|
@ -12,24 +11,15 @@ public class QueueManifest extends HybridManifest {
|
|||
bindAndExpose(QueueClientConfiguration.class)
|
||||
.to(QueueClientConfigurationImpl.class);
|
||||
|
||||
bindAndExpose(QueueClient.class);
|
||||
bindAndExpose(Exchange.Direct.class);
|
||||
bindAndExpose(Exchange.Fanout.class);
|
||||
bindAndExpose(Exchange.Topic.class);
|
||||
bindAndExpose(PrimaryQueue.class);
|
||||
bindAndExpose(QueueClient.class).asEagerSingleton();
|
||||
bindAndExpose(Exchange.Direct.class).asEagerSingleton();
|
||||
bindAndExpose(Exchange.Fanout.class).asEagerSingleton();
|
||||
bindAndExpose(Exchange.Topic.class).asEagerSingleton();
|
||||
bindAndExpose(PrimaryQueue.class).asEagerSingleton();
|
||||
|
||||
publicBinder().forOptional(MessageQueue.class)
|
||||
.setBinding().to(PrimaryQueue.class);
|
||||
|
||||
// These will connect in the order listed here.
|
||||
// TODO: figure out the order from their dependencies.
|
||||
final ConnectableBinder services = new ConnectableBinder(publicBinder());
|
||||
services.addBinding().to(QueueClient.class);
|
||||
services.addBinding().to(Exchange.Direct.class);
|
||||
services.addBinding().to(Exchange.Fanout.class);
|
||||
services.addBinding().to(Exchange.Topic.class);
|
||||
services.addBinding().to(PrimaryQueue.class);
|
||||
|
||||
final SuspendableBinder suspendables = new SuspendableBinder(publicBinder());
|
||||
suspendables.addBinding().to(PrimaryQueue.class);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package tc.oc.api.minecraft;
|
|||
|
||||
import com.google.inject.Provides;
|
||||
import tc.oc.api.config.ApiConfiguration;
|
||||
import tc.oc.api.connectable.ConnectableBinder;
|
||||
import tc.oc.api.docs.Server;
|
||||
import tc.oc.api.docs.virtual.ServerDoc;
|
||||
import tc.oc.api.minecraft.config.MinecraftApiConfiguration;
|
||||
|
@ -24,9 +23,6 @@ public final class MinecraftApiManifest extends HybridManifest {
|
|||
@Override
|
||||
protected void configure() {
|
||||
bind(ServerDoc.Identity.class).to(Server.class);
|
||||
|
||||
new ConnectableBinder(binder())
|
||||
.addBinding().to(MinecraftServiceImpl.class);
|
||||
}
|
||||
|
||||
@Provides Server localServer(MinecraftService minecraftService) {
|
||||
|
@ -53,6 +49,6 @@ public final class MinecraftApiManifest extends HybridManifest {
|
|||
bindAndExpose(MinecraftApiConfiguration.class).to(MinecraftApiConfigurationImpl.class);
|
||||
|
||||
bindAndExpose(MinecraftService.class).to(MinecraftServiceImpl.class);
|
||||
bindAndExpose(MinecraftServiceImpl.class); // Needs to be exposed so it can be registered as a connectable service
|
||||
bind(MinecraftServiceImpl.class).asEagerSingleton();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import net.md_5.bungee.api.chat.TranslatableComponent;
|
|||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import tc.oc.api.annotations.ApiRequired;
|
||||
import tc.oc.api.bukkit.users.BukkitUserStore;
|
||||
import tc.oc.api.docs.PlayerId;
|
||||
import tc.oc.api.docs.virtual.EngagementDoc;
|
||||
|
@ -54,7 +53,6 @@ import tc.oc.pgm.victory.VictoryMatchModule;
|
|||
/**
|
||||
* Responsible for creating/updating {@link EngagementDoc}s.
|
||||
*/
|
||||
@ApiRequired
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class EngagementMatchModule extends MatchModule implements Listener {
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import javax.inject.Inject;
|
|||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import tc.oc.api.annotations.ApiRequired;
|
||||
import tc.oc.api.docs.virtual.MapDoc;
|
||||
import tc.oc.api.docs.virtual.MatchDoc;
|
||||
import tc.oc.api.docs.virtual.ServerDoc;
|
||||
|
@ -25,7 +24,6 @@ import tc.oc.pgm.match.MatchModule;
|
|||
import tc.oc.pgm.match.MatchScope;
|
||||
import tc.oc.pgm.teams.events.TeamResizeEvent;
|
||||
|
||||
@ApiRequired
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class MatchPublishingMatchModule extends MatchModule implements Listener {
|
||||
class Update implements ServerDoc.MatchStatusUpdate {
|
||||
|
|
|
@ -9,7 +9,7 @@ import org.bukkit.event.EventHandler;
|
|||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import java.time.Instant;
|
||||
import tc.oc.api.annotations.ApiRequired;
|
||||
|
||||
import tc.oc.api.bukkit.users.BukkitUserStore;
|
||||
import tc.oc.api.docs.Participation;
|
||||
import tc.oc.api.docs.Server;
|
||||
|
@ -26,7 +26,6 @@ import tc.oc.pgm.match.MatchScope;
|
|||
import tc.oc.pgm.match.Party;
|
||||
import tc.oc.pgm.teams.Team;
|
||||
|
||||
@ApiRequired
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class ParticipationPublishingMatchModule extends MatchModule implements Listener {
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@ import javax.inject.Provider;
|
|||
|
||||
import com.google.inject.TypeLiteral;
|
||||
import org.bukkit.event.Listener;
|
||||
import tc.oc.api.connectable.Connector;
|
||||
import tc.oc.api.annotations.ApiRequired;
|
||||
import tc.oc.commons.core.reflect.Types;
|
||||
import tc.oc.pgm.match.Match;
|
||||
import tc.oc.pgm.match.MatchModule;
|
||||
|
@ -27,15 +25,12 @@ import tc.oc.pgm.module.ModuleManifest;
|
|||
*/
|
||||
public abstract class MatchModuleManifest<M extends MatchModule> extends ModuleManifest<MatchModule, MatchScoped, MatchModuleContext, M> {
|
||||
|
||||
protected final boolean apiRequired;
|
||||
|
||||
protected MatchModuleManifest() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
protected MatchModuleManifest(@Nullable TypeLiteral<M> type) {
|
||||
super(type);
|
||||
this.apiRequired = rawType.isAnnotationPresent(ApiRequired.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -49,13 +44,9 @@ public abstract class MatchModuleManifest<M extends MatchModule> extends ModuleM
|
|||
}
|
||||
|
||||
@Inject protected Provider<Match> matchProvider;
|
||||
@Inject private Connector connector;
|
||||
|
||||
@Override
|
||||
protected final Optional<M> provisionModuleWithoutDependencies() throws ModuleLoadException {
|
||||
if(apiRequired && !connector.isConnected()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return provisionModuleWithoutFiltering()
|
||||
.filter(MatchModule::shouldLoad);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import org.bukkit.event.EventPriority;
|
|||
import org.bukkit.event.Listener;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import tc.oc.api.annotations.ApiRequired;
|
||||
|
||||
import tc.oc.api.docs.Server;
|
||||
import tc.oc.api.docs.virtual.DeathDoc;
|
||||
import tc.oc.api.model.BatchUpdater;
|
||||
|
@ -37,7 +37,6 @@ import tc.oc.pgm.tracker.damage.SpleefInfo;
|
|||
import tc.oc.pgm.tracker.damage.TNTInfo;
|
||||
import tc.oc.pgm.tracker.damage.TrackerInfo;
|
||||
|
||||
@ApiRequired
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class DeathPublishingMatchModule extends MatchModule implements Listener {
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.bukkit.Location;
|
|||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import java.time.Instant;
|
||||
import tc.oc.api.annotations.ApiRequired;
|
||||
|
||||
import tc.oc.api.docs.Objective;
|
||||
import tc.oc.api.docs.Server;
|
||||
import tc.oc.api.model.IdFactory;
|
||||
|
@ -27,7 +27,6 @@ import tc.oc.pgm.wool.PlayerWoolPlaceEvent;
|
|||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ApiRequired
|
||||
@ListenerScope(MatchScope.LOADED)
|
||||
public class ObjectivePublishingMatchModule extends MatchModule implements Listener {
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.lang.annotation.Annotation;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Binding;
|
||||
|
@ -21,6 +22,8 @@ import com.google.inject.spi.Elements;
|
|||
import com.google.inject.spi.ProvisionListener;
|
||||
import com.google.inject.spi.TypeEncounter;
|
||||
import com.google.inject.spi.TypeListener;
|
||||
import tc.oc.commons.core.reflect.ResolvableType;
|
||||
import tc.oc.commons.core.reflect.TypeArgument;
|
||||
import tc.oc.commons.core.reflect.TypeParameter;
|
||||
import tc.oc.commons.core.reflect.TypeResolver;
|
||||
import tc.oc.commons.core.reflect.Types;
|
||||
|
@ -161,6 +164,18 @@ public interface Binders extends ForwardingBinder {
|
|||
return membersInjector(TypeLiteral.get(type));
|
||||
}
|
||||
|
||||
class EagerProvisioner<T> {
|
||||
@Inject EagerProvisioner(T t) {}
|
||||
}
|
||||
|
||||
default <T> void provisionEagerly(Key<T> key) {
|
||||
bind(key.ofType(new ResolvableType<EagerProvisioner<T>>(){}.with(new TypeArgument<T>(key.getTypeLiteral()){})))
|
||||
.asEagerSingleton();
|
||||
}
|
||||
|
||||
default <T> void provisionEagerly(TypeLiteral<T> type) { provisionEagerly(Key.get(type)); }
|
||||
default <T> void provisionEagerly(Class<T> type) { provisionEagerly(Key.get(type)); }
|
||||
|
||||
@Override
|
||||
default Binders withSource(Object source) {
|
||||
return wrap(ForwardingBinder.super.withSource(source));
|
||||
|
|
Loading…
Reference in New Issue