Merge lp://qastaging/~mixxxdevelopers/mixxx/engine-control-refactor into lp://qastaging/~mixxxdevelopers/mixxx/trunk

Proposed by RJ Skerry-Ryan
Status: Needs review
Proposed branch: lp://qastaging/~mixxxdevelopers/mixxx/engine-control-refactor
Merge into: lp://qastaging/~mixxxdevelopers/mixxx/trunk
Diff against target: 6049 lines (+2116/-1120)
60 files modified
mixxx/build/depends.py (+4/-0)
mixxx/src/basetrackplayer.cpp (+19/-12)
mixxx/src/basetrackplayer.h (+1/-0)
mixxx/src/cachingreader.cpp (+36/-15)
mixxx/src/cachingreader.h (+9/-1)
mixxx/src/engine/bpmcontrol.cpp (+39/-24)
mixxx/src/engine/bpmcontrol.h (+30/-28)
mixxx/src/engine/callbackcontrolmanager.cpp (+178/-0)
mixxx/src/engine/callbackcontrolmanager.h (+159/-0)
mixxx/src/engine/callbacktrackmanager.cpp (+150/-0)
mixxx/src/engine/callbacktrackmanager.h (+121/-0)
mixxx/src/engine/clockcontrol.cpp (+22/-12)
mixxx/src/engine/clockcontrol.h (+9/-7)
mixxx/src/engine/cuecontrol.cpp (+95/-151)
mixxx/src/engine/cuecontrol.h (+38/-39)
mixxx/src/engine/enginebuffer.cpp (+182/-120)
mixxx/src/engine/enginebuffer.h (+56/-53)
mixxx/src/engine/enginebufferscalest.cpp (+0/-16)
mixxx/src/engine/enginebufferscalest.h (+0/-4)
mixxx/src/engine/enginechannel.cpp (+13/-6)
mixxx/src/engine/enginechannel.h (+10/-16)
mixxx/src/engine/engineclipping.cpp (+16/-23)
mixxx/src/engine/engineclipping.h (+13/-12)
mixxx/src/engine/enginedeck.cpp (+26/-17)
mixxx/src/engine/enginedeck.h (+11/-9)
mixxx/src/engine/enginefilterblock.cpp (+68/-49)
mixxx/src/engine/enginefilterblock.h (+21/-14)
mixxx/src/engine/engineflanger.cpp (+30/-13)
mixxx/src/engine/engineflanger.h (+5/-5)
mixxx/src/engine/enginemaster.cpp (+71/-33)
mixxx/src/engine/enginemaster.h (+26/-16)
mixxx/src/engine/enginemicrophone.cpp (+14/-7)
mixxx/src/engine/enginemicrophone.h (+10/-5)
mixxx/src/engine/enginepassthrough.cpp (+27/-16)
mixxx/src/engine/enginepassthrough.h (+12/-7)
mixxx/src/engine/enginepregain.cpp (+44/-30)
mixxx/src/engine/enginepregain.h (+18/-19)
mixxx/src/engine/enginestate.h (+33/-0)
mixxx/src/engine/enginevinylsoundemu.cpp (+26/-25)
mixxx/src/engine/enginevinylsoundemu.h (+15/-11)
mixxx/src/engine/enginevumeter.cpp (+22/-18)
mixxx/src/engine/enginevumeter.h (+13/-10)
mixxx/src/engine/engineworker.cpp (+2/-1)
mixxx/src/engine/loopingcontrol.cpp (+74/-44)
mixxx/src/engine/loopingcontrol.h (+31/-27)
mixxx/src/engine/positionscratchcontroller.cpp (+26/-17)
mixxx/src/engine/positionscratchcontroller.h (+6/-6)
mixxx/src/engine/quantizecontrol.cpp (+31/-16)
mixxx/src/engine/quantizecontrol.h (+10/-11)
mixxx/src/engine/ratecontrol.cpp (+79/-68)
mixxx/src/engine/ratecontrol.h (+28/-26)
mixxx/src/engine/readaheadmanager.cpp (+0/-6)
mixxx/src/engine/readaheadmanager.h (+0/-2)
mixxx/src/engine/syncworker.cpp (+28/-0)
mixxx/src/engine/syncworker.h (+24/-0)
mixxx/src/engine/vinylcontrolcontrol.cpp (+70/-37)
mixxx/src/engine/vinylcontrolcontrol.h (+10/-13)
mixxx/src/mixxx.cpp (+2/-1)
mixxx/src/trackinfoobject.h (+2/-1)
mixxx/src/util.h (+1/-1)
To merge this branch: bzr merge lp://qastaging/~mixxxdevelopers/mixxx/engine-control-refactor
Reviewer Review Type Date Requested Status
Mixxx Development Team Pending
Review via email: mp+114572@code.qastaging.launchpad.net

Description of the change

Eliminate almost all use of mutexes within the engine.

This branch drastically refactors the use of the control system within the engine and eliminates a variety of threading issues that led to liberal use of mutexes within engine code. With every lock of a mutex comes the possibility of blocking the callback so this branch should reduce the probability of xruns occurring. As an added benefit it reduces the complexity of some engine logic.

The branch introduces a wrapper for ControlObjects called a 'CallbackControl'. This wrapper communicates with the control system in the GUI thread via a lock-free FIFO. When another part of Mixxx changes a control, the valueChanged() signal from a CallbackControl is not delivered until the start of the callback period (and in the callback thread). Similarly, any changes to CallbackControls by the engine are written to a lock-free FIFO and proxied to the ControlObjects they wrap by the SyncWorker that runs in the engine-worker thread pool after every callback.

Some EngineControls listen to signals from TrackInfoObjects (such as beatsChanged and cuesChanged signals). This was unsafe because it could execute engine code in a non-callback thread. This branch introduces a callback-safe CallbackTrackWatcher class which listens for signals from a TrackInfoObject and re-broadcasts them at the start of the engine callback in the callback thread. This way no engine code is ever executed by a non-callback thread.

CachingReader now delivers its signals in the callback thread instead of the engine worker thread pool. This prevents executing engine code in a non-callback thread.

Not all mutex locks are gone. There are still some (more subtle) ones remaining that will take a lot more work to get rid of.
* Every access of TrackInfoObject methods locks a mutex.
* Every access of a Beats class locks a mutex.
* EngineBuffer still broadcasts certain signals that result in a QueuedConnection to BaseTrackCache. This involves coordinating with the Qt event loop which I assume involves locking a mutex (maybe it uses a lock-free FIFO).
* Writing to the EngineSidechain
* Probably more that I've missed.

To post a comment you must log in.
Revision history for this message
RJ Skerry-Ryan (rryan) wrote :

(First commit to this branch was 10/2011.. it sure took long enough ;) )

Revision history for this message
Sean M. Pappalardo (pegasus-renegadetech) wrote :

I'm not qualified to review this so I just have some concerns & questions:

- The Qt docs attach warnings to specifying signal-slot connection types (e.g. Qt::DirectConnection,) saying that Qt automatically uses an appropriate one. (If the signal and slot are in the same thread, Qt::DirectConnection is used automatically.) They say if you find yourself needing to force this, then the threading model is suspect. http://qt-project.org/doc/qt-4.8/threads-qobject.html#signals-and-slots-across-threads
- Please explain your warning about CallbackControl::valueChanged*() as that seems to contradict the above (unless CC is expected to run in the thread of the slot, which would use DirectConnection anyway.)
- What does "callback-safe" mean?
- EngineClipping constructor: the comment for m_ctrlClipping still speaks of ControlPotMeter but the new code uses ControlObject. Which is correct?
- I know you didn't change this but while I'm looking at it, in EnginePassthrough::receiveBuffer: we should change the buffer overflow and underflow detection to assert on the conditional so if it happens, we know what failed. (Asserting on false isn't very informative.) For extra points, use Q_ASSERT_X and add "buffer over/underflow" text.

2925. By RJ Skerry-Ryan

Remove comment.

2926. By RJ Skerry-Ryan

Change Q_ASSERT to qWarning in EnginePassthrough

2927. By RJ Skerry-Ryan

add comments to src/engine/callbackcontrolmanager.h

Revision history for this message
RJ Skerry-Ryan (rryan) wrote :
Download full text (3.8 KiB)

Thanks for taking a look Sean!

On Thu, Jul 12, 2012 at 4:07 AM, Sean M. Pappalardo
<email address hidden>wrote:

> - The Qt docs attach warnings to specifying signal-slot connection types
> (e.g. Qt::DirectConnection,) saying that Qt automatically uses an
> appropriate one. (If the signal and slot are in the same thread,
> Qt::DirectConnection is used automatically.) They say if you find yourself
> needing to force this, then the threading model is suspect.
> http://qt-project.org/doc/qt-4.8/threads-qobject.html#signals-and-slots-across-threads

All controls and objects that are part of the mixing engine are created in
the main thread and as a result live in the main thread (according to Qt).
That means that for any AutoConnection in the engine code, if an emitted
signal is not in the main thread (e.g. the callback thread) it will be a
QueuedConnection and the slot will be invoked by the main thread. Qt can
never invoke a QueuedConnection using the callback thread because the
callback thread doesn't have an event loop. In order to make
AutoConnections work, we would have to move every single object in the
engine to the engine thread. This is a lot of code for little extra benefit
and a DirectConnection gives you assurance that the slot is a direct call
(which is always what we want in the engine -- we never want a
QueuedConnection ever so why give Qt the option?)

> - Please explain your warning about CallbackControl::valueChanged*() as
> that seems to contradict the above (unless CC is expected to run in the
> thread of the slot, which would use DirectConnection anyway.)
>

As I mentioned above, if you use an AutoConnection, any signal emitted from
engine code will have its slot invoked by the main thread, so all
CallbackControl signals should be connected to using a DirectConnection.

As for your point about CallbackControl running in the thread of the
signal, the signal is always emitted in the callback thread. This is
because whenever a ControlObject emits a valueChanged() signal, that
value-changed signal is written to a lock-free FIFO by
ControlWatcher/CallbackControlManager. At the beginning of the callback,
CallbackControlManager reads all the updates from this FIFO and updates all
the CallbackControls. This causes all the CallbackControls to emit
valueChanged() if they were changed, but the emit happens in the callback
thread. Since any slots connection to CallbackControls must use
DirectConnection, the slots are also run in the callback thread so
everything is safe. This is in direct contrast to trunk today in which you
connect directly to the ControlObject's valueChanged signal and your slot
is usually invoked by the main thread (and sometimes, the controller
thread).

> - What does "callback-safe" mean?
>

Callback-safe means
1) non-blocking .. calling a section of code, emitting a certain signal,
etc. will not cause the callback thread to block
2) the thread running the code is the callback -- this branch makes the
entire engine non-thread-safe so any code that would cause engine code to
be invoked in a thread other than the callback thread is not callback safe.

> - EngineClipping constructor: the comment for m_ctrlClippi...

Read more...

Revision history for this message
Daniel Schürmann (daschuer) wrote :

Hi RJ,

Thank you for refectoring. It is definitive a step in the right directions it looks quite good.

But reading your code and the legacy code I was first a little lost. Do we have a documentation about the details outside the code? I am afraid any new Mixxx developer will have the same experience.

Here are some comments:
* Why do you need CallbackControl::m_updates? It look like that it is never read.
* Qts emit is not lock free. Even if you use a direct connection, the "signalSlotLock" is used when checking if there is a queued connection. So we have to be sure we can always lock. I think this is the case because we do not connect or unconnect at runtime, right?
* I think you can reduce the Fifo size. If it is required to have a fifo bigger than ~3, I think we have a problem anyway because outdated values are processed.

---

Here some additional thoughts (I hope you will not kill me for that):

For my feeling we need a control object refactoring that is a little more advanced.
Since I have not understand the whole picture it may be rubbish, but I hope it helps nevertheless.

* For me a control object value always represents the current value of something NOW. I don't like the current concept with a sync thread which shovel the current values between the control object proxys. The effect is that we may have different values for one control object at the same time.
* The sync thread runs with the audio buffer size. This may triggers a qt Bug up to 4.7 which confuses the order of the slot updates.
* The current concept is very hard to understand at all for new developers.
* new / malloc are relative slow functions so the current concept with QQueue and sync is not the fastest one.
* I have still not managed to get a jerk free waveform progress. I am not sure if the control objects are part of the problem, but I have the feeling that the qt signal queue from Mixxx is quiet overcrowded.

Idea:
* The problem can be very easy solved on 64 bit architectures, because writing a double is an atomic operation. So setting a new value to a control object can be done from any thread directly to the control object value itself. The updates events can be handled by qt as usual, because its almost lock free if we ban connect and disconnect and all other locking functions.
* For the 32 bit architecture we may consider to introducer a 32 bit control object like int or float. (I like to have an int control object anyway). For the required double control objects, we need a ring buffer slightly different to that uses by RJ. It needs to have one element for each possible user thread to garantee lock free access. The current element should be marked by a QAtomicPointer. (Last writer wins)
http://woboq.com/blog/introduction-to-lockfree-programming.html does not entirely fit to our problem but gives a hint how it may work.

Kind regards,

Daniel

2928. By RJ Skerry-Ryan

Merging from lp:mixxx/1.11

Revision history for this message
Sean M. Pappalardo (pegasus-renegadetech) wrote :

Why aren't you updating from trunk if that's where this will end up?

2929. By RJ Skerry-Ryan

Merging from lp:mixxx.

2930. By RJ Skerry-Ryan

Delete merge detritus.

2931. By RJ Skerry-Ryan

Add missing createControls call.

2932. By RJ Skerry-Ryan

Add SyncWorker back in to sync CallbackControl updates from the CallbackControlManager after the callback runs.

2933. By RJ Skerry-Ryan

Merging from lp:mixxx.

2934. By RJ Skerry-Ryan

Merging from lp:mixxx.

2935. By RJ Skerry-Ryan

Merging from lp:mixxx.

Unmerged revisions

2935. By RJ Skerry-Ryan

Merging from lp:mixxx.

2934. By RJ Skerry-Ryan

Merging from lp:mixxx.

2933. By RJ Skerry-Ryan

Merging from lp:mixxx.

2932. By RJ Skerry-Ryan

Add SyncWorker back in to sync CallbackControl updates from the CallbackControlManager after the callback runs.

2931. By RJ Skerry-Ryan

Add missing createControls call.

2930. By RJ Skerry-Ryan

Delete merge detritus.

2929. By RJ Skerry-Ryan

Merging from lp:mixxx.

2928. By RJ Skerry-Ryan

Merging from lp:mixxx/1.11

2927. By RJ Skerry-Ryan

add comments to src/engine/callbackcontrolmanager.h

2926. By RJ Skerry-Ryan

Change Q_ASSERT to qWarning in EnginePassthrough

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
The diff is not available at this time. You can reload the page or download it.

Subscribers

People subscribed via source and target branches