Skip to content

Threading in an Unsafe World

July 28, 2014

So what do you do when you want to add threading to an application but other parts of the application are not thread safe? For example, lets look at Minecraft server mods and how I’ve implemented multithreading in CivCraft.

As of writing this the Bukkit API and Minecraft itself  are not thread safe meaning it’s not save to grab game data or change data from a thread. There are no protections preventing you from *trying* to access and change these game objects so sometimes it will seem to work, especially for servers with few players such as your test server. However rest assured you’ll get the dreaded  “ConcurrentModificationException” or worse just random crashes eventually.

For Minecraft servers, by far their most precious resource is how much time is spent during a Tick. By adding additional synchronous tasks to Minecraft we increase the delay between ticks and reduce our server’s TPS. To help combat this, I’ve tried to move as many tasks to asynchronous tasks as possible.

The textbook solution would be to establish locks on data that needs to be accessed by multiple threads and only allow one thread to work on it at a time. However in the case of our Minecraft server plugin, we simply do not have access to the parts of the code that we need in order to add locks. Too many sections of the code are public and we cannot simply modify the Minecraft server’s code. So are we doomed to run everything in a synchronous task? Nearly, but not quite.

In CivCraft the following tasks are completed using asynchronous timers and tasks:

  • Building Structures
  • Trommel Processing
  • Farm Growth Processing
  • Cottage/Granary Consumption

If you’re familiar with CivCraft, you’ll know that each of these tasks need to both obtain data from the world and set data in the world. Doing so directly is not possible since the main world is not thread safe, so how is it done? Consider the following pattern:

Basic Async Diagram

click to enlarge

 

In the diagram above, the main thread launches a asynchronous task with all of the data it needs to complete the task. As the asynchronous task completes parts of it’s task it Queues those bits of data into a thread-safe queue where the data is consumed by a synchronous task.

For example when a new structure is being built, from the main thread CivCraft launches a new BuildAsyncTask by providing the Town, Buildable object, and template that’s being built. The BuildAsyncTask performs the calculations for how long it should sleep in between building each block and waits the proper amount of time. When the BuildAsyncTask is ready to build a block it sends a “Block update” request into a queue where the SyncBuildTask will dequeue it and perform the build operation synchronously. Another example is the Farm Growth task. The main thread will take a snapshot of an entire chunk, and send that data over to the asynchronous thread which will then perform a search for blocks that could grow. Once the async thread determines which blocks are going to grow, it sends back growth requests to the main thread so that they can be performed synchronously.

This is all well and good for when you have all the data you need to start a task. But what if you need to retrieve data from an async task while it’s running? This can also be accomplished by using thread-safe queues to “request” data. Consider the following diagram:

Advanced Async Diagram

click to enlarge

When an asynchronous task requires data, it can request that data from the synchronous task and wait for the reply. This works, but has some obvious downsides. The first being that the Asynchronous task has to wait for the sync task and is blocked in the meantime. However as you may recall time in the synchronous thread is our most precious resource, so blocking an asynchronous thread is a small price to pay. A more serious problem exists with data integrity. Since data can change in the synchronous thread while it’s being worked on in the asynchronous thread, oblivious to the changes. This creates a race condition between the sync and async threads. In practice though on our Minecraft server data could only get out of sync if the async task took longer to complete than sync ticks and since our async tasks were much faster than that I ran into no race conditions. But be warned that they are there. To completely avoid a race condition you need to validate that the data being used to derive a update has not changed since the last tick and if it has, rollback the async task with new data and try again.

In conclusion, there are some tricks you can pull off if you’re stuck in a non-threadsafe environment to gain a bit of threading. Minecraft server plugins are a bit of a special case in that we’re simply unable to follow the standard advice of actually making code thread-safe in the first place. But if you’re stuck in a situation where you cannot modify a bit of data to be thread-safe (such as making a Minecraft plugin) hopefully this post will help you out a little.

From → Uncategorized

Leave a Comment

Leave a Reply