The following example contains three VBA modules: two classes and one regular. After I run RubberDuck VBA tests and then try to close Excel, Excel hangs while actively using the CPU. Running the tests once does not reproduce this issue every time, but when I do at least two runs, it seems that the issue is reproduced every time.
RDVBA Version 2.5.2.5871
OS: Microsoft Windows NT 6.2.9200.0, x64
Test environment 1:
Host Product: Microsoft Office XP x86
Host Version: 10.0.6501
Host Executable: EXCEL.EXE
Test environment 2:
Host Product: Microsoft Office 2016 x64
Host Version: 16.0.4266.1001
Host Executable: EXCEL.EXE
ModuleTests.bas
'@TestModule
Option Explicit
Option Private Module
Private Assert As Rubberduck.PermissiveAssertClass
#Const USE_ASSERT_OBJECT = True
'@ModuleInitialize
Private Sub ModuleInitialize()
Set Assert = New Rubberduck.PermissiveAssertClass
End Sub
'@ModuleCleanup
Private Sub ModuleCleanup()
Set Assert = Nothing
Debug.Print CStr(Timer()) & ": Assert = Nothing"
End Sub
'@TestMethod("Factory")
Private Sub ztcCreate_VerifiesDefaultManager()
Dim dbm As Class2
Set dbm = Class2.Create(ThisWorkbook.Path)
#If USE_ASSERT_OBJECT Then
Assert.IsNotNothing dbm
#Else
Assert.IsTrue Not dbm Is Nothing
#End If
End Sub
Class1.cls
'@PredeclaredId
Option Explicit
Public Function Create(Optional ByVal DefaultPath As String = vbNullString) As Class1
Dim Instance As Class1
Set Instance = New Class1
Set Create = Instance
End Function
Private Sub Class_Terminate()
Debug.Print CStr(Timer()) & ": Class1 Class_Terminate"
End Sub
Class2.cls
'@PredeclaredId
Option Explicit
Private Type TClass2
DllMan As Class1
End Type
Private this As TClass2
'@DefaultMember
Public Function Create(ByVal DllPath As String) As Class2
Dim Instance As Class2
Set Instance = New Class2
Instance.Init DllPath
Set Create = Instance
End Function
Friend Sub Init(ByVal DllPath As String)
Dim FileNames As Variant
Set this.DllMan = Class1.Create(DllPath)
End Sub
Private Sub Class_Terminate()
Debug.Print CStr(Timer()) & ": Class2 Class_Terminate"
End Sub
CodePudding user response:
That flickering that you are seeing when Excel is hanging is basically Excel trying to clear an object from memory but failing. I know that for sure because the same thing happens if inside a userform there is a private custom class that doesn't get set to Nothing
before the form unloads.
If you add this code to your Class2
:
Friend Sub Clear()
Set this.DllMan = Nothing
End Sub
and then update this:
Assert.IsNotNothing dbm
to this:
Assert.IsNotNothing dbm
dbm.Clear
inside the test method then the issue is gone.
Moreover, if I update the test method:
'@TestMethod("Factory")
Private Sub ztcCreate_VerifiesDefaultManager()
Dim dbm As Class2
Set dbm = Class2.Create(ThisWorkbook.Path)
#If USE_ASSERT_OBJECT Then
Assert.IsNotNothing dbm
Debug.Print "Before Clear"
dbm.Clear
Debug.Print "After Clear"
#Else
Assert.IsTrue Not dbm Is Nothing
#End If
Debug.Print "After Test"
End Sub
then after I run the test I get this in the Immediate window:
and only after about 7 seconds I get the last line:
This suggests to me that the Assert.IsNotNothing
holds on to the reference for longer than it should.
Edit #1
Removing the Clear
method and changing the Terminate event for Class2 to this:
Private Sub Class_Terminate()
Set this.DllMan = Nothing
Debug.Print CStr(Timer()) & ": Class2 Class_Terminate"
End Sub
seems to also solve the issue. The only difference is that now both classes get delayed, as expected. So, the delay itself doesn't seem to be an issue.
CodePudding user response:
I have amended the original code and ran a few more experiments exposing some odd behavior as illustrated below. While the nature of the problem is still unclear and appears to be associated with RDVBA (I think I now have sufficient evidence to create an RDVBA issue), I have narrowed down the problem and found a workaround.
In short, I originally had this test Assert.IsNotNothing dbm
, and with instrumented code, I observed odd termination timing/sequence. The amended code includes a conditional compilation structure for illustrative purposes. When Assert.IsTrue Not dbm Is Nothing
construct is selected instead, both symptoms and the issue are gone.