SLua: timers can overcorrect after enabling a disabled script
under review
Frio Belmonte
If you enable a script that was in disabled state and has a running timer, the
second
timer tick after waking up may end up happening much sooner than specified.I'm unsure if this is by spec or a bug, but Suzanna Linn's guide at least specifies timers should never tick faster than their specified rate.
Repro:
local t = 0;
local last = os.clock()
LLTimers:every(1, function()
t+=1
print("tick#", t, "delta", os.clock()-last)
last = os.clock()
end)
Play with the script running checkbox, turning it on and off, and observe the reported tick delta for the second tick. As one example, after having the script disabled for over a minute, the second tick after waking up was only 0.37 seconds instead of ~1.
This behavior is not present under LSL-Mono or LSL-SLua, second tick and further ticks are always ~1 s.
Log In
SuzannaLinn Resident
LLTimers is already checking for long delays.
A timer is triggered at most once per time frame (about 0.022 seconds). Timers shorter than this accumulate delay:
LLTimers:every(0.01, function(expected, interval)
print(expected, interval, ll.GetTime())
end)
-- > (after about 160 ticks)
2.877502999995468 0.01 4.845279000001028
2.887502999995468 0.01 4.867448000004515
2.8975029999954676 0.01 4.8899380000075325
2.9075029999954674 0.01 4.9117240000050515
2.917502999995467 0.01 4.9339900000049965
4.937502999995467 0.01 4.956663999997545
4.947502999995467 0.01 4.978514999995241
4.957502999995467 0.01 5.001050999999279
When the delay reaches 2 seconds, the passed intervals are skipped and the next interval is scheduled:
What about recalculating the next interval from the current time, and decreasing the "long delay" time to much less than 2 seconds for intervals longer than a time frame?
H
Harold Linden
marked this post as
under review
Thanks for the report!
I could believe this. If this only happening on the second tick after resuming and you don't see this behavior in LSL-SLua then it's probably an issue with the
LLTimers
implementation.There's some fairly complex scheduling and catchup logic in https://github.com/secondlife/slua/blob/main/VM/src/llltimers.cpp#L456 , I'll have a look at what's going on there.
Frio Belmonte
Just a couple more idle shower thoughts on this:
I can't find it written down anywhere that timers could never fire before their specified interval to correct for delays, even as far as LSL is concerned, so maybe it has never been precisely specified in public.
However, imagine the following scenario: you do something heavy or throttled (e.g. send httprequests) on a long interval timer that normally protects you against the throttling.
The script is stopped (or detached, as noted by Suzanna), then restarted/attached later: it could very well happen that the heavy event fires immediately, and then its "scheduled interval" kicks in right after, causing it to run the heavy event twice in a row, causing it to overrun what it was trying to protect against with the timer, in which case the LSL behavior feels more expected and correct.
Two options come to mind: voluntarily drop a timer tick following the script being resumed so the delay to the 2nd tick would be at most 2x but not below 1x, (but that would have to be done carefully too: maybe there was no catch-up tick needed if the script was stopped/detached only for a short time), or alternatively recompute the target tick times, latter matching the LSL style.
SuzannaLinn Resident
Frio Belmonte
I would rather recompute the target tick times.
Some scripts prefer a faster tick than dropping one, for instance the timer to scan people in a visitors counter.
Frio Belmonte
SuzannaLinn Resident Agreed, not that I have a full understanding of the timer code it already looks complicated so whatever would be the feasible adjustment to make.
SuzannaLinn Resident
It also happens when detaching and attaching
SuzannaLinn Resident
It schedules the next timer from the previous one and not from the current time:
LLTimers:every(1, function(expected, interval)
print(expected, interval, ll.GetTime())
end)
-- >
2.3562479999964125 1 2.3783199999888893
3.3562479999964125 1 3.3782549999887124
4.3562479999964125 1 4.356492999999318
5.3562479999964125 1 14.71184699999867
15.356247999996413 1 15.356292999989819
16.356247999996413 1 16.356380999990506
This is usually good to keep timers regular, otherwise, since there is always a very small delay, the timers would be slowly accumulating delay.
I don't know if it is also good in case of a long delay (because of script not running, sim restart, etc.).
I will update my SLua guide to explain it better. What I mean is that:
- The actual trigger time could be a few milliseconds later of the expected trigger time, never before.
But I have to add that:
- The interval could be slightly shorter, if the previous timer has had more delay than the current one.
Frio Belmonte
SuzannaLinn Resident Thanks, having them produce more accurate results over time instead of tick vs. tick which appears to be the case for LSL VMs makes sense. For one test I managed to have the 2nd tick go as low as ~0.2 s with a 1 second interval, so I guess that means I re-enabled the script N+0.8 seconds after initial launch, making it cram the first missed tick and then the second on-schedule one so close together. Your guide was the only reference discussing any aspects of the new LLTimers system I could think of hence the quote.
SuzannaLinn Resident
Frio Belmonte
Glad the Slua guide is useful! Thanks for using it.
Let's wait for Harold's insights on changing the LLTimers behaviour in case of long delays.