• Examples (HTML5)
• Examples (iOS)
• Examples (Android)
• Examples (C++)
Server API Documentation

 

» Java Extensions: advanced concepts

In this article we discuss advanced aspects of Java Extension development:

Since 2.9.0

» The Server threading model

NOTE: for all users of SFS2X 2.8.x and earlier versions please see the old threading model document.

SmartFoxServer 2X runs all Extensions in a multithreaded environment. There are fundamentally two separate thread pools operating on an Extension: the ExtensionController and the SystemController. The former entity is responsible for processing client requests while the latter dispatches system events such as LOGIN, USER_DISCONNECT, ROOM_VARIABLES_UPDATE, etc.

Since multiple threads can operate concurrently on the Extension code we need to make sure that access to shared state is handled correctly. The standard JDK's concurrent collections and locking features provide robust tools to handle the most common concurrency problems with minimal effort.

We highly recommend concurrent collections over native arrays or old-style collections, such as Vector and Hashtable, which can limit concurrency and create bottlenecks in the code. Also, when atomicity is required we suggest to look into AtomicInteger, AtomicLong, AtomicReference as a valid solution. Finally, locking classes will be required when complex operations need to be run in a mutually exclusive fashion.

We also suggest a couple of external resources if you want to dig deeper into this subject:

The following diagram illustrates what what we have described. The two controllers run separate thread pools that concurrently invoke request and event handlers on the Extension classes.

In addition to this, Extension code can also schedule any number of recurring or delayed tasks which in turn are handled by a different entity, the TaskScheduler, running its own thread pool.

It is important to note that the SFS2X API already take care of concurrency most of the times: all the calls provided bye the main SFSApi class are thread safe and the same goes for the Game API and Buddy List API. There are however a few exceptions: SFSObject and SFSArray, for example, are not thread safe. Since these objects are mostly used for data transport, they are usually not contended by multiple threads. In any case the Javadoc specifies which object require extra care for thread safety.

^top menu

 

Since 2.9.0

» Auto load-balancing Thread Pools

One of the question that gets asked very frequently is how to configure the amount of threads running in the system to scale an application correctly. In SmartFoxServer 2.9.0 we have introduced a significant improvement in scalability by removing manually configured thread pools.

The solution we provide is a self-monitoring, auto-scaling Executor that is able to scale on demand providing a balanced, yet configurable, ratio between latency and resources used.

With this feature the amount of manual configuration and fine tuning required to run any application is essentially reduced to zero. More importantly the system automatically watches the state of the message queues and reacts to a lack of threads on demand. This allows to protect the server from creating and destroying threads continuously for small bursts of traffic. At the same time it allows to react to significant increases in traffic and/or slow I/O work. Excess threads are going to be released as soon as they are no longer needed.

BOTTOM LINE: since version 2.9.0 you don't need to manually configure the maximum number of System/Extension threads, nor you need to worry about thread resources when adding slow I/O requests such as external databases and remote HTTP calls.

^top menu

 

Since 2.9.0

» Tuning thread pools

For all geeks out there we provide an advanced panel under the AdminTool > Server Configurator module that allows experts to fine tune the default configuration. The following is a list of available parameters for each Executor.

NOTE: we recommend not to touch the default configuration values unless you have very good reasons for doing so. The default settings are already fine tuned to provide excellent scalability in a wide array of scenarios and workloads.

^top menu

 

» Maintaining state in Extensions

When we employ the SFSExtension class as the base class for our Extensions we end up with one main Extension object and a series of request and event handlers. A common question is: where to keep the application shared state? (score, leaderboards, game data, etc)

Typically there are two logical answers:

We would highly recommend the first approach over the second for a series of reasons:

^top menu

 

» Class loading architecture

In the introductory article on Extensions we have mentioned that each Extension is loaded in a different ClassLoader in order to allow hot-redeploy during development or even production. We also illustrated that in order to share dependencies across multiple Extensions these ClassLoaders follow a specific hierarchy:

The diagram shows that each Extension "sees" all its deployed classes in its own ClassLoader, it can access the top global Extension classes thanks to its parent ClassLoader and finally it can use any classes from the SFS2X framework thanks to the topmost element in this hierarchy.

When an extension is reloaded only the bottom ClassLoader is destroyed and rebuilt. This will create new versions of the classes contained in the deployed jar file(s), while the rest of the classes in the top levels are unaffected (this means that they cannot be reloaded).

Let's examine a practical example to see how this can be useful. Suppose Extension A is our main Zone Extension, while B and C are two Room Extensions, governing two different games. We want Extensions B and C to communicate with A in order to access the game leaderboards.

The first problem we encounter is that we need to deploy the same model classes in all three Extensions (A, B and C) in order to properly run the example. This means that each ClassLoader contains a different version of the same Class, which is a well known problem in Java. The infamous ClassCastException is raised by the Java Runtime when you attempt to get an object from another context even if the two classes are the same bytecode. Technically, although they really are the same bytecode, the JVM sees them as different Class definitions that happen to share the same fully qualified name although in three separate contexts (ClassLoaders).

NOTE
If we didn't lost you up to this point you probably know the basics of ClassLoading in Java. If this sounds confusing we would highly recommend to check a few of articles on this subject:

Fortunately the solution is pretty easy: all you need to do is deploy the model classes in the extensions/__lib__/ folder and you will be able to share these objects across all Extensions.

Example:

ClassLoaders2

Every Extension ClassLoader is now able to access the same model classes because they are reachable in their parent Loader. By providing access to the game model from the main Zone Extension we have created a Singleton-like solution without worrying about static data.

If you still require to create one or more Singleton classes, that need to be shared across multiple Extensions, this approach will work too. You will need to deploy the Singleton(s) in a jar file inside the __lib__/ folder.

^top menu

 

» Delayed and Scheduled Tasks

Often times the game logic requires to use timers for recurring client updates (e.g. the end of a turn time, the triggering of specific events, NPC actions, etc).

A quick solution to this problem is using the ScheduledThreadPoolExecutor class provided in the JDK, which offers a convenient task executor backed by a pool of threads. SFS2X already runs its own instance of this Executor (wrapped in a class called TaskScheduler).

The following snippet of Java code shows how to run a looping task using the Server's own TaskScheduler.

public class SchedulerTest extends SFSExtension
{
	private class TaskRunner implements Runnable
	{
		private int runningCycles = 0;
		
		public void run()
		{
			runningCycles++;	
			trace("Inside the running task. Cycle:  " + runningCycles);
			
			if (runningCycles >= 10)
			{
				trace("Time to stop the task!");
				taskHandle.cancel();
			}
		}
	}
	
	// Keeps a reference to the task execution
	ScheduledFuture<?> taskHandle;
	
	@Override
	public void init()
	{
		SmartFoxServer sfs = SmartFoxServer.getInstance();
		
		// Schedule the task to run every second, with no initial delay
		taskHandle = sfs.getTaskScheduler().scheduleAtFixedRate(new TaskRunner(), 0, 1, TimeUnit.SECONDS);
	}
}

The scheduleAtFixedRate method takes four arguments:

  1. a Runnable object that will execute the Task's code
  2. the initial delay before the execution starts
  3. the interval at which the task will be executed
  4. the time unit used to express the time values

The Scheduler also exposes a schedule method that executes a Runnable task once after the specified amount of time. Finally the Scheduler's thread pool can be resized on-the-fly at runtime via the resizeThreadPool() method.

NOTE: the initial size of the system TaskScheduler's thread pool can be adjusted via the Server Configurator module in the AdminTool.

^top menu