diff --git a/pom.xml b/pom.xml
index 39313315..5c8c024f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -298,14 +298,6 @@
compile
-
-
- com.comphenix.executors
- BukkitExecutors
- 1.1-SNAPSHOT
- compile
-
-
junit
diff --git a/src/main/java/com/comphenix/protocol/ProtocolLib.java b/src/main/java/com/comphenix/protocol/ProtocolLib.java
index 46dc854a..e6b6781b 100644
--- a/src/main/java/com/comphenix/protocol/ProtocolLib.java
+++ b/src/main/java/com/comphenix/protocol/ProtocolLib.java
@@ -33,7 +33,7 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
-import com.comphenix.executors.BukkitExecutors;
+import com.comphenix.protocol.executors.BukkitExecutors;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DelegatedErrorReporter;
diff --git a/src/main/java/com/comphenix/protocol/executors/AbstractBukkitService.java b/src/main/java/com/comphenix/protocol/executors/AbstractBukkitService.java
new file mode 100644
index 00000000..216d7b1b
--- /dev/null
+++ b/src/main/java/com/comphenix/protocol/executors/AbstractBukkitService.java
@@ -0,0 +1,142 @@
+package com.comphenix.protocol.executors;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.*;
+
+import com.google.common.base.Throwables;
+import com.google.common.util.concurrent.ListenableScheduledFuture;
+
+import org.bukkit.scheduler.BukkitTask;
+
+abstract class AbstractBukkitService
+ extends AbstractListeningService implements BukkitScheduledExecutorService {
+
+ private static final long MILLISECONDS_PER_TICK = 50;
+ private static final long NANOSECONDS_PER_TICK = 1000000 * MILLISECONDS_PER_TICK;
+
+ private volatile boolean shutdown;
+ private final PendingTasks tasks;
+
+ public AbstractBukkitService(PendingTasks tasks) {
+ this.tasks = tasks;
+ }
+
+ @Override
+ protected RunnableAbstractFuture newTaskFor(Runnable runnable, T value) {
+ return newTaskFor(Executors.callable(runnable, value));
+ }
+
+ @Override
+ protected RunnableAbstractFuture newTaskFor(final Callable callable) {
+ validateState();
+ return new CallableTask(callable);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ validateState();
+
+ if (command instanceof RunnableFuture) {
+ tasks.add(getTask(command), (Future>) command);
+ } else {
+ // Submit it first
+ submit(command);
+ }
+ }
+
+ // Bridge to Bukkit
+ protected abstract BukkitTask getTask(Runnable command);
+ protected abstract BukkitTask getLaterTask(Runnable task, long ticks);
+ protected abstract BukkitTask getTimerTask(long ticksInitial, long ticksDelay, Runnable task);
+
+ @Override
+ public List shutdownNow() {
+ shutdown();
+ tasks.cancel();
+
+ // We don't support this
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void shutdown() {
+ shutdown = true;
+ }
+
+ private void validateState() {
+ if (shutdown) {
+ throw new RejectedExecutionException("Executor service has shut down. Cannot start new tasks.");
+ }
+ }
+
+ private long toTicks(long delay, TimeUnit unit) {
+ return Math.round(unit.toMillis(delay) / (double)MILLISECONDS_PER_TICK);
+ }
+
+ @Override
+ public ListenableScheduledFuture> schedule(Runnable command, long delay, TimeUnit unit) {
+ return schedule(Executors.callable(command), delay, unit);
+ }
+
+ @Override
+ public ListenableScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) {
+ long ticks = toTicks(delay, unit);
+
+ // Construct future task and Bukkit task
+ CallableTask task = new CallableTask(callable);
+ BukkitTask bukkitTask = getLaterTask(task, ticks);
+
+ tasks.add(bukkitTask, task);
+ return task.getScheduledFuture(System.nanoTime() + delay * NANOSECONDS_PER_TICK, 0);
+ }
+
+ @Override
+ public ListenableScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay,
+ long period, TimeUnit unit) {
+
+ long ticksInitial = toTicks(initialDelay, unit);
+ long ticksDelay = toTicks(period, unit);
+
+ // Construct future task and Bukkit task
+ CallableTask> task = new CallableTask