ExecutorService backed by Sponge's SchedulerService

I’ve been working on some (probably insane) proof of concepts that combines SpongeAPI with a few other libraries (primarily Akka).
To properly combine some of the asynchronous frameworks with Sponge in a thread-safe manner I’ve created an ScheduledExecutorService that executes all its work on the primary server thread. Any tasks that are executed in this manner can safely modify the game world/data.
Since some other people might find a use in it, so here is the source code.

Warning: If you haven’t worked with asynchronous code before this might not be the best place to start, you need to be very careful where stuff executes since the use of this resource can cause the primary server thread to block, which is bad.

Source: https://gist.github.com/Kiskae/5c44ea146857ca173170
Requires SpongeAPI 2.1+ (Since it uses the new SchedulerService)

Any library that accepts the Java 1.5 Executor/ExecutorService API can use this utility class to execute actions on the primary server thread.

  • RxJava: A custom Scheduler can be used - Schedulers.from(SpongeExecutorService.create(Game, PluginContainer))
  • Java8 CompletableFuture: All the *Async methods have an overload that accepts an Executor to run on.
  • Scala Futures: All futures take an implicit ExecutionContext - ExecutionContext.fromExecutorService(SpongeExecutorService.create(Game, PluginContainer))

Example with Java 8’s CompletableFuture:

final Server s = game.getServer();
ExecutorService executor = SpongeExecutorService.create(game, game.getPluginManager().getInstance(this).get())
ObserableFuture<UUID> futurePlayerId = someSource();
futurePlayerId.thenApplyAsync((playerUUID) -> {
  //Any actions performed here are thread-safe!
  final Player p = server.getPlayer(playerUUID).get();
  //Modify the world at the players location
  return player;
}, executor).thenApply((player) -> {
  //Beware, this also runs on the server thread, use another *Async call to move to another thread
  player.kick();
  return player.getName();
}).thenApplyAsync((playerName) -> {
  //This is no longer thread-safe, but will not risk blocking the main thread
  computeNameCoolnessVerySlow(playerName);
});

Q&A:
Why use this instead of SchedulerService?
SpongeExecutorContext wraps around the SchedulerService and makes it usable by any library to expects the default java Executor* interfaces.
This class makes it possible to integrate modern asynchronous libraries into plugins while allows for thread-safe access to the game world by scheduling the execution through SpongeExecutorContext, which delegates to the SchedulerService.

4 Likes

Could we get some example use-cases with code, as well as the benefits of using this over the existing SchedulerService?

Added some links to asynchronous libraries that can be combined with this class, as well as an example.
As to the difference from Sponge’s scheduler service, this utility class wraps that service and provides an interface that is compatible with the default task execution API.

Wow, this is very neat :+1:

I could see this working very well for a Scala wrapper on top of Sponge, nice work. Kind of makes me question why we use a custom scheduler interface at all when we can wrap existing Java libraries and interfaces over it.

The primary differences I can see is that there is no way to support scheduled work based on server ticks through the standard API.
Theres also the issue of tasks inheriting the executor, you need to be a lot more careful when using the executor since any additional/heavy tasks it runs can slow down the server.

Are thread safe or must be thread safe?

Are thread safe when talking about modifying the game world, since it will be executed on the server thread.

I must either not know how CompletableFuture works, or how to read, but I can swear you are calling that using “thenApplyAsync”

Yes, you can either use thenApply which executes the transformation on the thread in which the last transformation finished, thenApplyAsync without an executor to execute it in a common thread pool or thenApplyAsync with a specified executor to execute the transform on the specified executor. By specifying the SpongeExecutorService the transform will be executed on the main thread.

I don’t understand. You are saying that the code performed in thenApplyAsync is run synchronously?

I’ve got to agree with @AgentTrolldude. I’m fairly certain that the very nature of Asynchronous tasks makes them not thread safe.

I agree that the name is misleading, but it is by no means @Kiskae’s fault. If you actually look at the source for CompletableFuture, then you’d see that the nomenclature “async” here means that the submitted function is run asynchronously from the point of view of the current executor. This may mean that it is run synchronously in another thread, but doesn’t mean that it is necessarily run asynchronously on the supplied executor.

http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/concurrent/CompletableFuture.java#l2381

public <U> CompletableFuture<U> thenApplyAsync
    (Function<? super T,? extends U> fn,
     Executor executor) {
    if (executor == null) throw new NullPointerException();
    return doThenApply(fn, executor);
}

And note that the javadocs for thenApplyAsync don’t actually mention that the task is supposedly “run asynchronously”:

Returns a new CompletionStage that, when this stage completes normally, is executed using the supplied Executor, with this stage’s result as the argument to the supplied action.

In any case this has nothing to do with the tool @Kiskae provided and has everything to do with the Java standard library. I think that the criticisms here are misguided and miss the point of what the in this thread is trying to accomplish. It is simply providing an adaptor for users to use the concurrency mechanisms of the Java standard library on Sponge’s software.

To be honest, I think that when the Sponge java projects switch to Java 8 and these concurrency primitives become available, this interface or something like this interface should be provided as part of the API. I can see it being very useful for developers who have existing software written using the Java concurrency library and wish to simply run it on top of Sponge instead, cooperatively with their plugin software. That way anyone who wishes to can fully leverage the abstractions provided by the Java concurrency library without having to deal with the specialized contracts Sponge provides.

4 Likes

All the interfaces used by the utility are from Java 5, so you could already add it now and use concurrency libraries that are compatible with the standard library. If such utilities are added to SpongeAPI there probably also need to be a few concurrency debugging tools, such as a way to check on which thread the execution is taking place.

I also wonder if there is a way to make an action a one-shot, so any future actions are not performed on the main thread. (Can be emulated by adding <CompletableFuture>.thenApplyAsync(func, SpongeExecutorService).thenApplyAsync(Function.identity()) when running a task through the SpongeExecutorService)

True, but I personally don’t see many useful concurrency-related interfaces in Java until version 8. That’s when things start to get exciting with futures, completable futures, etc.

If you work with a good-enough IDE you should already have access to a debugger that tells you this.

Ah, I understand.

I don’t believe there was any criticism, apologies if my posts were interpreted that way.

I strongly recommend against making it standardized for a number of reasons:

  • SpongeAPI is inherently designed to work functionally
  • In an OOP language like Java, functional programming tends to break LoD
  • Overuse of lambada statements in statements such as the example posted in OP is verbose and hard to understand

While I am not suggesting the idea be thrown out completely, there’s criticism elsewhere of how functional the API [already] is. Again, just a reminder that overuse of functional programming in a non-functional language will make the code verbose and hard to read.

java.util.concurrent was introduced in Java 5?

Thread (Java Platform SE 7 ) + logger = win

Pass in a different executor?

well yeah, but a way to cleanly make sure only a single action is executed on the sponge thread

To be fair this utility is in no way tied to the functional programming style of Java 8, it serves as a basis on which most concurrency frameworks are built, which includes OO-style and functional style as well as more exotic systems (like akka actors)

Ah yes, I was getting carried away by the example :stuck_out_tongue:

Since the project has moved to Java 8, I’ve been asked to contribute this utility class to the project.
The Pull Request can be found here: https://github.com/SpongePowered/SpongeAPI/pull/829

2 Likes