From 331ebbf4f6db6defcf0f59d3cdd0b656269c6194 Mon Sep 17 00:00:00 2001 From: theminecoder Date: Fri, 8 May 2020 00:32:17 +1000 Subject: [PATCH] Add exception reporting event (#488) --- .../0055-Add-exception-reporting-event.patch | 699 ++++++++++++++++++ 1 file changed, 699 insertions(+) create mode 100644 BungeeCord-Patches/0055-Add-exception-reporting-event.patch diff --git a/BungeeCord-Patches/0055-Add-exception-reporting-event.patch b/BungeeCord-Patches/0055-Add-exception-reporting-event.patch new file mode 100644 index 0000000..399fb4c --- /dev/null +++ b/BungeeCord-Patches/0055-Add-exception-reporting-event.patch @@ -0,0 +1,699 @@ +From 9f488c256a9738d00aca1417cff6765090c877a4 Mon Sep 17 00:00:00 2001 +From: theminecoder +Date: Wed, 22 Apr 2020 14:00:44 +1000 +Subject: [PATCH] Add exception reporting event + + +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/event/ProxyExceptionEvent.java b/api/src/main/java/io/github/waterfallmc/waterfall/event/ProxyExceptionEvent.java +new file mode 100644 +index 00000000..ee6cb5dd +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/event/ProxyExceptionEvent.java +@@ -0,0 +1,27 @@ ++package io.github.waterfallmc.waterfall.event; ++ ++import com.google.common.base.Preconditions; ++import io.github.waterfallmc.waterfall.exception.ProxyException; ++import net.md_5.bungee.api.plugin.Event; ++ ++/** ++ * Called whenever an exception is thrown in a recoverable section of the server. ++ */ ++public class ProxyExceptionEvent extends Event { ++ ++ private ProxyException exception; ++ ++ public ProxyExceptionEvent(ProxyException exception) { ++ this.exception = Preconditions.checkNotNull(exception, "exception"); ++ } ++ ++ /** ++ * Gets the wrapped exception that was thrown. ++ * ++ * @return Exception thrown ++ */ ++ public ProxyException getException() { ++ return exception; ++ } ++ ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyCommandException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyCommandException.java +new file mode 100644 +index 00000000..644a4cd6 +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyCommandException.java +@@ -0,0 +1,64 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import net.md_5.bungee.api.CommandSender; ++import net.md_5.bungee.api.plugin.Command; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++ ++/** ++ * Thrown when a command throws an exception ++ */ ++public class ProxyCommandException extends ProxyException { ++ ++ private final Command command; ++ private final CommandSender commandSender; ++ private final String[] arguments; ++ ++ public ProxyCommandException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause); ++ this.commandSender = checkNotNull(commandSender, "commandSender"); ++ this.arguments = checkNotNull(arguments, "arguments"); ++ this.command = checkNotNull(command, "command"); ++ } ++ ++ public ProxyCommandException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(cause); ++ this.commandSender = checkNotNull(commandSender, "commandSender"); ++ this.arguments = checkNotNull(arguments, "arguments"); ++ this.command = checkNotNull(command, "command"); ++ } ++ ++ protected ProxyCommandException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ this.commandSender = checkNotNull(commandSender, "commandSender"); ++ this.arguments = checkNotNull(arguments, "arguments"); ++ this.command = checkNotNull(command, "command"); ++ } ++ ++ /** ++ * Gets the command which threw the exception ++ * ++ * @return exception throwing command ++ */ ++ public Command getCommand() { ++ return command; ++ } ++ ++ /** ++ * Gets the command sender which executed the command request ++ * ++ * @return command sender of exception thrown command request ++ */ ++ public CommandSender getCommandSender() { ++ return commandSender; ++ } ++ ++ /** ++ * Gets the arguments which threw the exception for the command ++ * ++ * @return arguments of exception thrown command request ++ */ ++ public String[] getArguments() { ++ return arguments; ++ } ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyEventException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyEventException.java +new file mode 100644 +index 00000000..b9842dc5 +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyEventException.java +@@ -0,0 +1,53 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import net.md_5.bungee.api.plugin.Event; ++import net.md_5.bungee.api.plugin.Listener; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++ ++/** ++ * Exception thrown when a server event listener throws an exception ++ */ ++//TODO Find a better way to retrieve the plugin for this. Currently the event register/bake process ++// doesnt leave a reference to the plugin so there's no way to know what one is being used at any given time ++public class ProxyEventException extends ProxyException { ++ ++ private final Listener listener; ++ private final Event event; ++ ++ public ProxyEventException(String message, Throwable cause, Listener listener, Event event) { ++ super(message, cause); ++ this.listener = checkNotNull(listener, "listener"); ++ this.event = checkNotNull(event, "event"); ++ } ++ ++ public ProxyEventException(Throwable cause, Listener listener, Event event) { ++ super(cause); ++ this.listener = checkNotNull(listener, "listener"); ++ this.event = checkNotNull(event, "event"); ++ } ++ ++ protected ProxyEventException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Listener listener, Event event) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ this.listener = checkNotNull(listener, "listener"); ++ this.event = checkNotNull(event, "event"); ++ } ++ ++ /** ++ * Gets the listener which threw the exception ++ * ++ * @return event listener ++ */ ++ public Listener getListener() { ++ return listener; ++ } ++ ++ /** ++ * Gets the event which caused the exception ++ * ++ * @return event ++ */ ++ public Event getEvent() { ++ return event; ++ } ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyException.java +new file mode 100644 +index 00000000..b9fbbbe8 +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyException.java +@@ -0,0 +1,23 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++/** ++ * Wrapper exception for all exceptions that are thrown by the server. ++ */ ++public class ProxyException extends Exception { ++ ++ public ProxyException(String message) { ++ super(message); ++ } ++ ++ public ProxyException(String message, Throwable cause) { ++ super(message, cause); ++ } ++ ++ public ProxyException(Throwable cause) { ++ super(cause); ++ } ++ ++ protected ProxyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ } ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyInternalException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyInternalException.java +new file mode 100644 +index 00000000..acc64c29 +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyInternalException.java +@@ -0,0 +1,34 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import io.github.waterfallmc.waterfall.event.ProxyExceptionEvent; ++import net.md_5.bungee.api.ProxyServer; ++ ++/** ++ * Thrown when the internal server throws a recoverable exception. ++ */ ++public class ProxyInternalException extends ProxyException { ++ ++ public ProxyInternalException(String message) { ++ super(message); ++ } ++ ++ public ProxyInternalException(String message, Throwable cause) { ++ super(message, cause); ++ } ++ ++ public ProxyInternalException(Throwable cause) { ++ super(cause); ++ } ++ ++ protected ProxyInternalException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ } ++ ++ public static void reportInternalException(Throwable cause) { ++ try { ++ ProxyServer.getInstance().getPluginManager().callEvent(new ProxyExceptionEvent(new ProxyInternalException(cause)));; ++ } catch (Throwable t) { ++ t.printStackTrace(); // Don't want to rethrow! ++ } ++ } ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginEnableDisableException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginEnableDisableException.java +new file mode 100644 +index 00000000..54dc7da0 +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginEnableDisableException.java +@@ -0,0 +1,20 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import net.md_5.bungee.api.plugin.Plugin; ++ ++/** ++ * Thrown whenever there is an exception with any enabling or disabling of plugins. ++ */ ++public class ProxyPluginEnableDisableException extends ProxyPluginException { ++ public ProxyPluginEnableDisableException(String message, Throwable cause, Plugin responsiblePlugin) { ++ super(message, cause, responsiblePlugin); ++ } ++ ++ public ProxyPluginEnableDisableException(Throwable cause, Plugin responsiblePlugin) { ++ super(cause, responsiblePlugin); ++ } ++ ++ protected ProxyPluginEnableDisableException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { ++ super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); ++ } ++} +\ No newline at end of file +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginException.java +new file mode 100644 +index 00000000..b0de7a4b +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginException.java +@@ -0,0 +1,36 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import net.md_5.bungee.api.plugin.Plugin; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++ ++/** ++ * Wrapper exception for all cases to which a plugin can be immediately blamed for ++ */ ++public class ProxyPluginException extends ProxyException { ++ public ProxyPluginException(String message, Throwable cause, Plugin responsiblePlugin) { ++ super(message, cause); ++ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); ++ } ++ ++ public ProxyPluginException(Throwable cause, Plugin responsiblePlugin) { ++ super(cause); ++ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); ++ } ++ ++ protected ProxyPluginException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin) { ++ super(message, cause, enableSuppression, writableStackTrace); ++ this.responsiblePlugin = checkNotNull(responsiblePlugin, "responsiblePlugin"); ++ } ++ ++ private final Plugin responsiblePlugin; ++ ++ /** ++ * Gets the plugin which is directly responsible for the exception being thrown ++ * ++ * @return plugin which is responsible for the exception throw ++ */ ++ public Plugin getResponsiblePlugin() { ++ return responsiblePlugin; ++ } ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginMessageException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginMessageException.java +new file mode 100644 +index 00000000..9e24e09c +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyPluginMessageException.java +@@ -0,0 +1,64 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import net.md_5.bungee.api.connection.ProxiedPlayer; ++import net.md_5.bungee.api.plugin.Plugin; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++ ++/** ++ * Thrown when an incoming plugin message channel throws an exception ++ */ ++public class ProxyPluginMessageException extends ProxyPluginException { ++ ++ private final ProxiedPlayer player; ++ private final String channel; ++ private final byte[] data; ++ ++ public ProxyPluginMessageException(String message, Throwable cause, Plugin responsiblePlugin, ProxiedPlayer player, String channel, byte[] data) { ++ super(message, cause, responsiblePlugin); ++ this.player = checkNotNull(player, "player"); ++ this.channel = checkNotNull(channel, "channel"); ++ this.data = checkNotNull(data, "data"); ++ } ++ ++ public ProxyPluginMessageException(Throwable cause, Plugin responsiblePlugin, ProxiedPlayer player, String channel, byte[] data) { ++ super(cause, responsiblePlugin); ++ this.player = checkNotNull(player, "player"); ++ this.channel = checkNotNull(channel, "channel"); ++ this.data = checkNotNull(data, "data"); ++ } ++ ++ protected ProxyPluginMessageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Plugin responsiblePlugin, ProxiedPlayer player, String channel, byte[] data) { ++ super(message, cause, enableSuppression, writableStackTrace, responsiblePlugin); ++ this.player = checkNotNull(player, "player"); ++ this.channel = checkNotNull(channel, "channel"); ++ this.data = checkNotNull(data, "data"); ++ } ++ ++ /** ++ * Gets the channel to which the error occurred from recieving data from ++ * ++ * @return exception channel ++ */ ++ public String getChannel() { ++ return channel; ++ } ++ ++ /** ++ * Gets the data to which the error occurred from ++ * ++ * @return exception data ++ */ ++ public byte[] getData() { ++ return data; ++ } ++ ++ /** ++ * Gets the player which the plugin message causing the exception originated from ++ * ++ * @return exception player ++ */ ++ public ProxiedPlayer getPlayer() { ++ return player; ++ } ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxySchedulerException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxySchedulerException.java +new file mode 100644 +index 00000000..0d574ecc +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxySchedulerException.java +@@ -0,0 +1,37 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import net.md_5.bungee.api.scheduler.ScheduledTask; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++ ++/** ++ * Thrown when a plugin's scheduler fails with an exception ++ */ ++public class ProxySchedulerException extends ProxyPluginException { ++ ++ private final ScheduledTask task; ++ ++ public ProxySchedulerException(String message, Throwable cause, ScheduledTask task) { ++ super(message, cause, task.getOwner()); ++ this.task = checkNotNull(task, "task"); ++ } ++ ++ public ProxySchedulerException(Throwable cause, ScheduledTask task) { ++ super(cause, task.getOwner()); ++ this.task = checkNotNull(task, "task"); ++ } ++ ++ protected ProxySchedulerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, ScheduledTask task) { ++ super(message, cause, enableSuppression, writableStackTrace, task.getOwner()); ++ this.task = checkNotNull(task, "task"); ++ } ++ ++ /** ++ * Gets the task which threw the exception ++ * ++ * @return exception throwing task ++ */ ++ public ScheduledTask getTask() { ++ return task; ++ } ++} +diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyTabCompleteException.java b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyTabCompleteException.java +new file mode 100644 +index 00000000..5bf57ec1 +--- /dev/null ++++ b/api/src/main/java/io/github/waterfallmc/waterfall/exception/ProxyTabCompleteException.java +@@ -0,0 +1,22 @@ ++package io.github.waterfallmc.waterfall.exception; ++ ++import net.md_5.bungee.api.CommandSender; ++import net.md_5.bungee.api.plugin.Command; ++ ++/** ++ * Called when a tab-complete request throws an exception ++ */ ++public class ProxyTabCompleteException extends ProxyCommandException { ++ ++ public ProxyTabCompleteException(String message, Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause, command, commandSender, arguments); ++ } ++ ++ public ProxyTabCompleteException(Throwable cause, Command command, CommandSender commandSender, String[] arguments) { ++ super(cause, command, commandSender, arguments); ++ } ++ ++ protected ProxyTabCompleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Command command, CommandSender commandSender, String[] arguments) { ++ super(message, cause, enableSuppression, writableStackTrace, command, commandSender, arguments); ++ } ++} +diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java +index 70d3736b..431d4d6f 100644 +--- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java ++++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java +@@ -24,6 +24,11 @@ import java.util.Stack; + import java.util.jar.JarEntry; + import java.util.jar.JarFile; + import java.util.logging.Level; ++import io.github.waterfallmc.waterfall.event.ProxyExceptionEvent; // Waterfall ++import io.github.waterfallmc.waterfall.exception.ProxyCommandException; // Waterfall ++import io.github.waterfallmc.waterfall.exception.ProxyEventException; // Waterfall ++import io.github.waterfallmc.waterfall.exception.ProxyPluginEnableDisableException; // Waterfall ++import io.github.waterfallmc.waterfall.exception.ProxyTabCompleteException; // Waterfall + import lombok.RequiredArgsConstructor; + import net.md_5.bungee.api.ChatColor; + import net.md_5.bungee.api.CommandSender; +@@ -31,6 +36,7 @@ import net.md_5.bungee.api.ProxyServer; + import net.md_5.bungee.api.connection.ProxiedPlayer; + import net.md_5.bungee.event.EventBus; + import net.md_5.bungee.event.EventHandler; ++import net.md_5.bungee.event.EventHandlerMethod; //Waterfall - Exception event + import org.yaml.snakeyaml.Yaml; + import org.yaml.snakeyaml.constructor.Constructor; + import org.yaml.snakeyaml.introspector.PropertyUtils; +@@ -178,10 +184,9 @@ public final class PluginManager + } + + String[] args = Arrays.copyOfRange( split, 1, split.length ); +- try +- { + if ( tabResults == null ) + { ++ try { // Waterfall - split command & tab complete exception handlers for exception event + if ( proxy.getConfig().isLogCommands() ) + { + proxy.getLogger().log( Level.INFO, "{0} executed command: /{1}", new Object[] +@@ -190,18 +195,28 @@ public final class PluginManager + } ); + } + command.execute( sender, args ); ++ // Waterfall start - split command & tab complete exception handlers for exception event ++ } catch ( Exception ex ) { ++ sender.sendMessage( ChatColor.RED + "An internal error occurred whilst executing this command, please check the console log for details." ); ++ ProxyServer.getInstance().getLogger().log( Level.WARNING, "Error in dispatching command", ex ); ++ this.callEvent( new ProxyExceptionEvent( new ProxyCommandException( ex, command, sender, args ) ) ); //Waterfall - throw error event ++ } ++ // Waterfall end + } else if ( commandLine.contains( " " ) && command instanceof TabExecutor ) + { ++ try { // Waterfall - split command & tab complete exception handlers for exception event + for ( String s : ( (TabExecutor) command ).onTabComplete( sender, args ) ) + { + tabResults.add( s ); + } +- } +- } catch ( Exception ex ) +- { ++ // Waterfall start - split command & tab complete exception handlers for exception event ++ } catch ( Exception ex ) { + sender.sendMessage( ChatColor.RED + "An internal error occurred whilst executing this command, please check the console log for details." ); + ProxyServer.getInstance().getLogger().log( Level.WARNING, "Error in dispatching command", ex ); ++ this.callEvent( new ProxyExceptionEvent( new ProxyTabCompleteException( ex, command, sender, args ) ) ); //Waterfall - throw error event + } ++ // Waterfall end ++ } + return true; + } + +@@ -289,7 +304,11 @@ public final class PluginManager + } ); + } catch ( Throwable t ) + { +- ProxyServer.getInstance().getLogger().log( Level.WARNING, "Exception encountered when loading plugin: " + plugin.getDescription().getName(), t ); ++ // Waterfall start - throw exception event ++ String msg = "Exception encountered when loading plugin: " + plugin.getDescription().getName(); ++ ProxyServer.getInstance().getLogger().log( Level.WARNING, msg, t ); ++ this.callEvent( new ProxyExceptionEvent( new ProxyPluginEnableDisableException( msg, t, plugin) ) ); ++ // Waterfall end + } + } + } +@@ -431,7 +450,7 @@ public final class PluginManager + Preconditions.checkNotNull( event, "event" ); + + long start = System.nanoTime(); +- eventBus.post( event ); ++ eventBus.post( event, this::handleEventException ); //Waterfall - pass exception handler below + event.postCall(); + + long elapsed = System.nanoTime() - start; +@@ -445,6 +464,14 @@ public final class PluginManager + return event; + } + ++ //Waterfall start - Exception handler passed to event bus to fire the exception event ++ private void handleEventException(String msg, T event, EventHandlerMethod method, Throwable ex) { ++ if( !(event instanceof ProxyExceptionEvent) ) { ++ this.callEvent( new ProxyExceptionEvent( new ProxyEventException( msg, ex, (Listener) method.getListener(), event) ) ); ++ } ++ } ++ //Waterfall end ++ + /** + * Register a {@link Listener} for receiving called events. Methods in this + * Object which wish to receive events must be annotated with the +diff --git a/event/src/main/java/net/md_5/bungee/event/EventBus.java b/event/src/main/java/net/md_5/bungee/event/EventBus.java +index f5bd1f98..d7c3b90d 100644 +--- a/event/src/main/java/net/md_5/bungee/event/EventBus.java ++++ b/event/src/main/java/net/md_5/bungee/event/EventBus.java +@@ -33,7 +33,8 @@ public class EventBus + this.logger = ( logger == null ) ? Logger.getLogger( Logger.GLOBAL_LOGGER_NAME ) : logger; + } + +- public void post(Object event) ++ // Waterfall - Add generic to signature so we don't have to cast in exception handler ++ public void post(T event, EventExceptionHandler exceptionHandler) + { + EventHandlerMethod[] handlers = byEventBaked.get( event.getClass() ); + +@@ -52,7 +53,9 @@ public class EventBus + throw new Error( "Method rejected target/argument: " + event, ex ); + } catch ( InvocationTargetException ex ) + { +- logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() ); ++ String msg = MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ); ++ logger.log( Level.WARNING, msg, ex.getCause() ); ++ if( exceptionHandler != null ) exceptionHandler.handleEventException( msg, event, method, ex ); //Waterfall - call passed exception handler + } + } + } +diff --git a/event/src/main/java/net/md_5/bungee/event/EventExceptionHandler.java b/event/src/main/java/net/md_5/bungee/event/EventExceptionHandler.java +new file mode 100644 +index 00000000..088fe9b7 +--- /dev/null ++++ b/event/src/main/java/net/md_5/bungee/event/EventExceptionHandler.java +@@ -0,0 +1,7 @@ ++package net.md_5.bungee.event; ++ ++public interface EventExceptionHandler { ++ ++ public void handleEventException(String msg, T event, EventHandlerMethod method, Throwable ex); ++ ++} +diff --git a/event/src/test/java/net/md_5/bungee/event/EventBusTest.java b/event/src/test/java/net/md_5/bungee/event/EventBusTest.java +index 2c737675..72f14937 100644 +--- a/event/src/test/java/net/md_5/bungee/event/EventBusTest.java ++++ b/event/src/test/java/net/md_5/bungee/event/EventBusTest.java +@@ -14,14 +14,14 @@ public class EventBusTest + public void testNestedEvents() + { + bus.register( this ); +- bus.post( new FirstEvent() ); ++ bus.post( new FirstEvent(), null ); // Waterfall - We dont need an exception handler here + Assert.assertEquals( 0, latch.getCount() ); + } + + @EventHandler + public void firstListener(FirstEvent event) + { +- bus.post( new SecondEvent() ); ++ bus.post( new SecondEvent(), null ); // Waterfall - We dont need an exception handler here + Assert.assertEquals( 1, latch.getCount() ); + latch.countDown(); + } +@@ -39,4 +39,5 @@ public class EventBusTest + public static class SecondEvent + { + } ++ + } +diff --git a/event/src/test/java/net/md_5/bungee/event/EventPriorityTest.java b/event/src/test/java/net/md_5/bungee/event/EventPriorityTest.java +index 351d3724..88f1346f 100644 +--- a/event/src/test/java/net/md_5/bungee/event/EventPriorityTest.java ++++ b/event/src/test/java/net/md_5/bungee/event/EventPriorityTest.java +@@ -15,7 +15,7 @@ public class EventPriorityTest + { + bus.register( this ); + bus.register( new EventPriorityListenerPartner() ); +- bus.post( new PriorityTestEvent() ); ++ bus.post( new PriorityTestEvent(), null ); // Waterfall - We dont need an exception handler here + Assert.assertEquals( 0, latch.getCount() ); + } + +diff --git a/event/src/test/java/net/md_5/bungee/event/UnregisteringListenerTest.java b/event/src/test/java/net/md_5/bungee/event/UnregisteringListenerTest.java +index fbfbd546..ae85a1d4 100644 +--- a/event/src/test/java/net/md_5/bungee/event/UnregisteringListenerTest.java ++++ b/event/src/test/java/net/md_5/bungee/event/UnregisteringListenerTest.java +@@ -13,7 +13,7 @@ public class UnregisteringListenerTest + { + bus.register( this ); + bus.unregister( this ); +- bus.post( new TestEvent() ); ++ bus.post( new TestEvent(), null ); // Waterfall - We dont need an exception handler here + } + + @EventHandler +diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +index a839cba5..a963c748 100644 +--- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java ++++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +@@ -11,6 +11,8 @@ import com.google.gson.Gson; + import com.google.gson.GsonBuilder; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import io.github.waterfallmc.waterfall.conf.WaterfallConfiguration; ++import io.github.waterfallmc.waterfall.event.ProxyExceptionEvent; ++import io.github.waterfallmc.waterfall.exception.ProxyPluginEnableDisableException; + import io.netty.bootstrap.ServerBootstrap; + import io.netty.channel.Channel; + import io.netty.channel.ChannelException; +@@ -476,7 +478,11 @@ public class BungeeCord extends ProxyServer + } + } catch ( Throwable t ) + { +- getLogger().log( Level.SEVERE, "Exception disabling plugin " + plugin.getDescription().getName(), t ); ++ // Waterfall start - throw exception event ++ String msg = "Exception disabling plugin " + plugin.getDescription().getName(); ++ getLogger().log( Level.SEVERE, msg, t ); ++ pluginManager.callEvent( new ProxyExceptionEvent( new ProxyPluginEnableDisableException( msg, t, plugin) ) ); ++ // Waterfall end + } + getScheduler().cancel( plugin ); + plugin.getExecutorService().shutdownNow(); +diff --git a/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java b/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java +index 67db0cc4..02ec98fc 100644 +--- a/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java ++++ b/proxy/src/main/java/net/md_5/bungee/scheduler/BungeeTask.java +@@ -3,6 +3,9 @@ package net.md_5.bungee.scheduler; + import java.util.concurrent.TimeUnit; + import java.util.concurrent.atomic.AtomicBoolean; + import java.util.logging.Level; ++ ++import io.github.waterfallmc.waterfall.event.ProxyExceptionEvent; ++import io.github.waterfallmc.waterfall.exception.ProxySchedulerException; + import lombok.Data; + import net.md_5.bungee.api.ProxyServer; + import net.md_5.bungee.api.plugin.Plugin; +@@ -63,7 +66,11 @@ public class BungeeTask implements Runnable, ScheduledTask + task.run(); + } catch ( Throwable t ) + { +- ProxyServer.getInstance().getLogger().log( Level.SEVERE, String.format( "Task %s encountered an exception", this ), t ); ++ //Waterfall start - throw exception event ++ String msg = String.format( "Task %s encountered an exception", this ); ++ ProxyServer.getInstance().getLogger().log( Level.SEVERE, msg, t ); ++ ProxyServer.getInstance().getPluginManager().callEvent( new ProxyExceptionEvent( new ProxySchedulerException( msg, t, this ) ) ); ++ //Waterfall end + } + + // If we have a period of 0 or less, only run once +-- +2.21.0 (Apple Git-122) +