Button::repeatTimerCallback maxing out CPU


#1

hi all,

this may be only windows related, but it might be relevant to the general juce population so ill post it here…

i came across a weird problem when using a component with a scrollbar (viewport). every now and then when clicking the scrollbar button the app would hang while the CPU would max out, sometimes for 10 seconds or more and then return back. i thought it was something i was doing wrong until i found i could replicate the same behaviour when running the jucedemo.exe downloaded from jules’ site.

now… it turns out that i could stop this from happening by setting the affinity for the process to run on one CPU only (i have a dual core athlon) which gave me some things to think about.

through debugging i found the app was stuck in the Button::repeatTimerCallback function, where it was doing some ridiculous number of iterations of the numTimesToCallback loop.

the particular part in juce_button.cpp is line 590

this was returning the maximum int value when now and lastTimeCallbackTime were equal. which was strange because i would assume these times would normally never be equal. however im guessing that because i have effectively 2 CPUs, each CPU could run the time queries in parallel and return the same value. :shock:

however i thought that shouldnt cause a major problem as the jmax macro should handle the situation where now == lastTimeCallbackTime yet it turns out it must have been getting confused with the varied types provided to the macro.

to fix this i ended up explicitly casting the inputs to the jmax function like so:

which solves the specific problem i was having, but im not sure whether this is something that might affect a wider range of situations, hence the post.


#2

oh, and while i noticed this was happing in JUCE 1.25 i’ve confimed the same behaviour (and fix) occurs with 1.26


#3

yeesh - well spotted.

I guess that’s yet another good reason for me to upgrade my clunky old PC to something a bit faster!


#4

Ok - to follow this up: The problem was that if the time went “backwards”, the unsigned subtraction would overflow. A neater fix would just be:

        const int numTimesToCallback = jmax (1, (jmax (lastTimeCallbackTime, now) - lastTimeCallbackTime) / repeatSpeed);

(Which is also safer if the timer wraps back to zero).

But the real question is how the hell the time went backwards between calls. Do the two cores have separate hi-res timers?..


#5

…and I’ll just try that again, having actually tried to compile it…

const int numTimesToCallback = (now > lastTimeCallbackTime) ? jmax (1, (now - lastTimeCallbackTime) / repeatSpeed) : 1;


#6

time going backwards? 8)

im not sure how the 2 cores deal with clocks… though i guess they both have seperate timers to some degree … though that would mean the only way you could guarantee a timers results would be to compare times from the same cpu core, which seems a bit retarded. also im unaware of how to schedule functions to run on a particular cpu, though to be honest i havent looked.

all this clever hyper-threading/sse type magic the cpu’s can do these days sure does complicate things sometimes.

FYI whenever this occurred it looked like i had exactly the same values for the two times, rather than the value for now being lower … still weird


#7

It’s an interesting point - the approximate timer gets updated by more than one thread, so if they were each a little bit out, it could jiggle about a bit. I’ve added some code to correct for this now.

Also had a scan through all the code and found a few other places where a timer going backwards could cause arithmetic errors, so I’ll toughen them up a bit. I think in most cases there’d be no problem because all the calls to getMillisecondTimer() are done on the same thread, but better safe than sorry.


#8

Yes, this is something big in game programming. Multiple cores will have different timings, keep that in a forefront of your mind when you are dealing with timing issues across possible cores.


#9