VBA.Timer midnight issue
6DiegoDiego9 opened this issue · comments
During last days I kept an always-on SeleniumVBA loop running with a 2000ms sleep each run.
Each morning, I found the script hung.
After the nth night crash, I investigated and found that it was simply waiting for the VBA.Timer to reach an overlimit number equivalent to 24:00:02 that could never reach because at midnight VBA.Timer resets to 0.
I thought about a couple of solutions to this bug:
- adding the VBA.Date to VBA.Timer: CLng(VBA.Date) + (CDbl(VBA.Timer) / 86400)
- going with the Windows API timers, that never reset
Since I already fine-tuned my all-purpose Sleep procedure for my master library, reaching my goal of <0.0% CPU usage, DoEvents, accuracy +-<10ms, I thought it would be a good idea to move it to the WebShared module for use by SeleniumVBA too, as in my last pull request.
I was also about to modify your WaitUntilReady procedure:
'@Description("Waits until element is interactable")
Public Function WaitUntilReady(element As WebElement, Optional ByVal maxWaitTimeMS As Long = 30000) As WebElement
'waits until element is interactable, returns the input element for further action
'such as "Click" on same line
'see https://www.w3.org/TR/webdriver/#element-displayedness
Dim startTime As Single
Dim nowTime As Single
Dim endTime As Single
startTime = VBA.Timer
nowTime = startTime
endTime = startTime + maxWaitTimeMS / 1000#
Do While nowTime < endTime
If element.IsDisplayed Then Exit Do
nowTime = VBA.Timer()
Dim elapsedTime As Single
If nowTime < startTime Then
endTime = endTime - elapsedTime
startTime = 0
End If
elapsedTime = nowTime - startTime
DoEvents 'yield to other processes.
Loop
Set WaitUntilReady = element
End Function
using the Windows API functions, like this:
'@Description("Waits until element is interactable")
Public Function WaitUntilReady(element As WebElement, Optional ByVal maxWaitTimeMS As Long = 30000) As WebElement
'waits until element is interactable, returns the input element for further action
'such as "Click" on same line
'see https://www.w3.org/TR/webdriver/#element-displayedness
Dim cTimeStart As Currency, cTimeNow As Currency
Dim dTimeElapsed As Currency, cMaxWaitTimeMS As Currency
getTime cTimeStart
Static cPerSecond As Currency
If cPerSecond = 0 Then getFrequency cPerSecond
cMaxWaitTimeMS = CCur(maxWaitTimeMS) * (cPerSecond / 1000)
Do
If element.IsDisplayed Then Exit Do
getTime cTimeNow
DoEvents 'yield to other processes
Loop Until (cTimeNow - cTimeStart) >= cMaxWaitTimeMS
Set WaitUntilReady = element
End Function
or at least using the VBA.Date to VBA.Timer solution,
but I didn't understand its "If nowTime < startTime" branch:
I can only think of a nowTime that is less than startTime when midnight passes, however I can't understand what the following lines accomplish. They don't seem to approach the midnight issue.
So, I leave my pull request and this last function modification for your review @GCuser99.
@6DiegoDiego9 thanks for your tireless (?) work in catching the mid-night error - I have to admit that I never tested it at mid-night. Sorry about that.
I'm ok with your new Sleep and WaitUntilReady methods. For the Sleep method, what advantage does it have over just using the API sleep? Would it only be the inclusion of DoEvents? Or are there other advantages?
Also, I noticed that you commented out the Private Module statement in WebShared.bas. Did you have a compelling reason for that? I put that line in there because I didn't like having WebShared methods exposed to calling projects - seems like all of that is under-the-hood type stuff.
Thanks!
API sleep: yes, the missing DoEvents was the only reason why I didn't use it, if I don't forget other reasons.
Option Private Module: hmm yes you're right, we should better manage that. Unfortunately the the "Friend" keyword doesn't work in standard modules.
Although I've no problem redeclaring getFrequency, getTime in another module where I need them for all-purpose high-precision timers, I would dislike to duplicate an entire all-purpose procedure like Sleep.
Hmmm, what about making WebShared a pre-declared public class so that we have the full choice of public, friend and private keywords?
To avoid to explicitly type the class name every time, I/we could then eventually put a Sleep procedure in a standard module that calls WebShared.Sleep.