High Quality JMS Messaging.

Introduction

The Threadpool Swiftlet manages the SwiftMQrouter thread pools.

The design of SwiftMQ is influenced by a stage design (see University of Berkeley, SEDA architecture). However, we don't call it stage, we call it task. Each task is assigned to a thread pool. This assignment is configurable but one should never change it. During the processing, several tasks could be involved. For example if a message is produced by a JMS client, it arrives at the reader task in the Network Swiftlet. If all necessary data is read (for nonblocking I/O), a JMS Swiftlet listener is called. This listener is a connection task and constructs the request out of the SMQP stream and then forwards it to a producer task that puts the message into the queue and forwards a SMQP reply into the connection outbound queue which is served by a writer task that writes to the output stream (via the Network Swiftlet). The other way, a message should be consumed: The SMQP request arrives as before but is forwarded finally to a consumer task. This task registers a message processor (which is a task as well) at the message queue. The queue itself is responsible for invoking the message processor to process a message. Processing a message is a consumer task that forwards the message to a session outbound queue (to ensure that messages are in sequence). This queue is served by a session delivery task that forwards the message to the connection outbound queue. Then the message is written (as a SMQP request) to the output stream. It is much more complex, but this is in short words how SwiftMQ works in principle.

Forwarding to [some task] means dispatching this task into the assigned thread pool. The thread pool is managed by the Threadpool Swiftlet. A thread pool consists of a pool queue which is a fast ring buffer and threads to execute dispatched tasks. There are several attributes to configure a thread pool. Please have a look at the "Tuning" section.

Due to the completely asynchronous behaviour of SwiftMQ, it is very scalable. However, scalability is a matter of configuring the thread pools. One can get very bad results if a thread pool isn't properly configured. E.g. on Linux, thread creation is a very heavy weight operation, as well as thread context switching. So, in the first case, you should have a minimum pool size to ensure that threads don't die, and in the second case you should set a maximum size to avoid massive context switching. The default settings fit most requirements. For further information consult the "Tuning" section.

Tuning

This section explains the different settings of thread pools and their consequences:

Minimum Threads

If a thread pool is created, the number of threads is pre-created and will never die. One should always have "some" minimum threads per pool if that pool is often frequented, such as JMS pools. An adequate minimum thread setting will prevent that threads will die after the idle timeout and thus must be recreated if a new task instance is dispatched to the pool. Recreation of threads is, especially under Linux, very expensive. However, one should not specify too many threads to keep the resource consumption low. The best way is to watch the "Usage" entity table of the Threadpool Swiftlet from the SwiftMQ Explorer to the average usage per pool. This is a good value for the minimum thread setting.

Maximum Threads

This setting avoids resource exhaustion and is also used to guarantee that, in some pools, only 1 thread will be executed at all. E.g. the Log Manager of the Store Swiftlet uses his own pool and there should never be more than 1 thread running within that pool. Therefore, one should never change this setting if it is specified within the default configuration.

Queue Length Threshold and Additional Threads

These two attributes specify the pool growing policy. During each dispatch of a task to a pool, it is checked whether the pool is out of threads. This is the case if no idle thread is available. Normally, if a pool is configured with a minimum size of threads, these threads handle the load. At peaks, if more tasks are dispatched, the pool's queue grows up. By specifying a queue length threshold, one can define how large the pool's queue may grow until additional threads are started. Per default, both attributes have a value of 1, that is, if no idle thread is available during a task dispatch, 1 additional thread will be started. E.g. to handle peak loads, one can specify that 5 additional threads should be started if the queue length reaches 10. If the queue length is below 10, no additional threads are started.

Idle Timeout

The idle timeout specifies the time in milliseconds after which a thread will die in the case that it is idle. If this value is too low, threads might die too early and you will get into the re-creation problem. If it is too high, threads might be idle too long and you may consume too much threads. So it really depends on the interval in which tasks are dispatched to the pool. The default idle timeout is 120000 milliseconds and should fit most requirements.

Priority

This setting specifies the thread priority of this pool. We have never seen that this has an effect at all concerning the processing behaviour. Moreover, in most cases, this setting was not respected by the JVM. The default is normal priority (5). You may change this and may get some other results, especially on multiprocessor machines.