Skip to content

Revamped Control System

RJ Skerry-Ryan edited this page Oct 1, 2009 · 25 revisions

Summary and Rationale

Status: This specification is in drafting. Feel free to add ideas to this page.

Mixxx's ControlObject system is lacking a few desirable features. This project will build a new Control system for thread-safe value communication across the Mixxx codebase. The new control system will run alongside the old system as is it slowly phased out.

Use Cases

  • Thread safe communication of basic value types (string, boolean, double, integer, etc)
  • Unified method for changing and retrieving system parameters across different Mixxx subsystems (MIDI, GUI, Script, OSC, etc)
  • Unified namespace for referring to control values across Mixxx
  • Triggering on changes to control values

Motivation and Design Issues

When designing pieces of Mixxx, every value that needs to be shared across threads must be guarded by locks in order to prevent race conditions that can lead to invalid data, nasty race conditions, and mysterious segfaults. Mixxx receives contributions from developers around the world, many of which do not have the time to invest in fully understanding the Mixxx codebase, including which thread runs in which context. For any given piece of code, a new developer may not be able to easily determine the threading model, and which threads run in which sections of code. Furthermore, if a given value in the Mixxx code needs to be shared across threads, the most common pattern for making it thread-safe is to make a per-variable lock, and guarding every use of the variable with the lock.

A Control system resolves these two issues by automatically protecting every use of a variable with a lock, and providing a 'worry-free' approach to thread safety where the contributor does not have to worry about the thread safety of their code as long as their values are in the Control system.

There are two competing interests while designing a thread safe value communication system: thread safety and performance. The problem with the above approach is that if every reference to a value is guarded by a lock, then there can be a significant performance impact if every variable in the code is protected in this manner.

The Existing Control System

The original Mixxx control system, the ControlObject system, balances the above concerns with a hybrid approach. This amounts to a two-tier system. There are ControlObjects, and ControlObject proxies. ControlObjects, which guard a double variable, supports two main methods: set() and get(). The double value in the ControlObject is not guarded by a lock at all. The intent is that each value that is in the control system has an 'owner'. The owner is the section of code that initially created the ControlObject. The owner of the ControlObject is the only code which is allowed to use the raw ControlObject itself. This allows raw (i.e. high-speed) access to the control variable. A ControlObject proxy, (e.g. ControlObjectThread and ControlObjectThreadMain) represents a non-owner section of the code which wishes to be retrieve the control value, change the control value, and be updated of changes to the control value. The ControlObject and its proxies operate asynchronously. Periodically, there is a synchronization step which occurs. All updates to the control object and all updates to the proxy are queued and during the synchronization step, all the latest value of the control object is broadcasted to each proxy, and the changes from the proxy are set on the ControlObject. This synchronization step is currently run at the beginning of the audio callback. The idea is that this is a time when no engine code can possibly be running. However, there is nothing preventing ControlObjects in other sections of Mixxx from being vulnerable to race conditions. The reason this is not a problem in practice is that ControlObject's are primarily created within the engine. In the future, this may not remain true as more and more features are added to Mixxx.

Implementation Performance

There are bottlenecks with the implementation of this system. The synchronization step is facilitated by three static queues, each with its own corresponding lock. The ControlObject set/get methods and the ControlObject proxy set/get methods all must use these static queues. This means that this system is actually far less performant then it claims to be. What it does achieve is that errant ControlObject proxies cannot cause lock contention on an engine ControlObject until the synchronization phase. Errant ControlObject code can cause Mixxx-wide contention for set/get operations on every ControlObject since the lock is static.

Overview of ControlObject Proxies

ControlObjectThread and ControlObjectThreadMain are two different types of ControlObject proxies which can be used. ControlObjectThread uses locks for mutual exclusion when receiving updates from its corresponding ControlObject. ControlObjectThreadMain assumes that the only code which will be calling its methods is running in the Qt GUI thread. In order to ensure mutual exclusion, ControlObjectThreadMain uses the Qt event loop to deliver updates from the ControlObject. This allows it to forego the use of locks. Unfortunately, the implementation does not include a set/get that doesn't use locks, so there is no real performance increase. ControlObjectThread can be used in any context, while ControlObjectThreadMain can only be used when it will only be used by code running from the GUI thread.

Proposed Design

Work Breakdown

This work breakdown structure (WBS) will become more detailed as the design above becomes more thorough and complete.

Team

Clone this wiki locally