v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class vars

Report problems with documented functionality
iseahound
Posts: 696
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class vars

Post by iseahound » 29 Apr 2021, 20:41

v1

Code: Select all

class s {

   __New() {
      this.start()
   }

   __Delete() {
      this.shutdown()
   }

   static instances := 0

   start() {
      s.instances++
      MsgBox % s.instances
   }

   shutdown() {
      s.instances--
      MsgBox % s.instances

      if (s.instances < 0)
         throw Exception("I don't understand why this error shows.")
   }
}

; r := new s() ; Works.
u := new s() ; Doesn't work.
ExitApp
v2

Code: Select all

class s {

   __New() {
      this.start()
   }

   __Delete() {
      this.shutdown()
   }

   static instances := 0

   start() {
      s.instances++
      MsgBox s.instances
   }

   shutdown() {
      s.instances--
      MsgBox s.instances

      if (s.instances < 0)
         throw Error("I don't understand why this error shows.")
   }
}

; r := s() ; Works.
u := s() ; Doesn't work.
ExitApp
I wish I could make this up, but there is clearly a data race going on somewhere? If the name of the class is s, then all letters before s, including r, a word beginning with r, works. If the letter is greater than the class name, then __Delete fails to retrieve the instance variable. No idea why the name of the variable being assigned to is of significance, unless everything is ordered in AHK alphabetically, and upon ExitApp, everything is unloaded alphabetically, causing class s to be unloaded before variable u, rendering s.__Delete()'s static var invalid?
iseahound
Posts: 696
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

Re: v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class

Post by iseahound » 29 Apr 2021, 22:27

Oh that's why this bug feels so familiar. I've probably encountered it thousands of times but assumed it was my poor coding.

As a simple workaround, Allow ExitApp to suppress thrown errors. i.e. try ExitApp
swagfag
Posts: 4500
Joined: 11 Jan 2017, 17:59

Re: v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class

Post by swagfag » 30 Apr 2021, 20:21

iseahound wrote:
29 Apr 2021, 22:27
As a simple workaround, Allow ExitApp to suppress thrown errors. i.e. try ExitApp
sure, u could do that

Code: Select all

#Requires AutoHotkey v2.0-a133-9819cb2d

ExitApp.OriginalExitApp := ExitApp.GetMethod('Call') ; retrieve and store the original implementation
ExitApp.DefineProp('Call', {Call: __SupressibleExitApp}) ; .DefineProp required, since ExitApp's Call is read-only
__SupressibleExitApp(this, ExitCode := 0) {
	OnError(IgnoreOnceHandler, 1) ; "Call the function after any previously registered functions."
	IgnoreOnceHandler(Thrown, Mode) {
		; immediately deregister the handler
		; u could skip this since ExitApp is going to kill the script regardless
		; but might be useful/needed if this pattern is applied to other functions 
		OnError(%A_ThisFunc%, 0)

		return 1 ; "Suppress the default error dialog and any remaining error callbacks."
	}

	this.OriginalExitApp(ExitCode)
}
but why? wasnt the point something had to be done with/to the class?
the simpler workaround would be to keep an internal reference to whatever it is u need to still remain alive at the time ur destructors are being called and use that internal reference instead
iseahound
Posts: 696
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

Re: v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class

Post by iseahound » 30 Apr 2021, 21:54

I actually don't care about properly freeing/releasing objects on script exit, and I'm more concerned about the ExitApp function being hung up. Windows is very resilient, and it will very likely free up resources when the process is freed. ExitApp needs to exit the script, otherwise it serves no real purpose. It's not a debugging tool. It should try its best and after 3 seconds forcefully quit the script. An OK dialog is not ok when exiting the script. Imagine being unable to shutdown your computer because you have 5 unsaved notepad windows. Perhaps ExitApp Force is more felicitous.
iseahound
Posts: 696
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

Re: v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class

Post by iseahound » 30 Apr 2021, 22:11

Code: Select all

Run % "TASKKILL /PID " DllCall("GetCurrentProcessId") "/F /T",, hide
solves the problem.
lexikos
Posts: 7518
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class

Post by lexikos » 01 May 2021, 02:37

... the behaviour you've described is by design. If that design doesn't suit your purpose, that's too bad for you. But it's not a bug. __Delete is called after the last reference to the object is released, which must always be after the variable which contained it is freed. It is not feasible to detect the "perfect order" in which objects should be released, and when circular dependencies exist, it is actually impossible. Therefore, the simplest and most predictable approach is taken: free the variables in a predetermined but undocumented order.

Any self-contained destructor will work correctly, because any references that the class has to other objects will not be released until after the destructor returns. However, circular references will prevent any of the connected objects from being freed.

If you wanted, you could prevent destruction of each class by storing its reference in an array somewhere, and then perform your cleanup of classes in whatever order you liked.
Source: [done!]variable environment release too early when ExitApp - AutoHotkey Community
Actually, I considered removing this - i.e. not calling __delete for global/static objects at all - but didn't really care enough to come to a decision. If you're defining __delete for a global/static object, you probably want it to be called.

I'm more concerned about the ExitApp function being hung up.
What are you talking about?

If __delete is being called on global/static objects, the program is already exiting. It cannot be cancelled, only postponed (such as by an infinite loop or MsgBox). If there are errors, dismiss the error dialogs and the script will exit. Then go fix the error. If your script had an OnExit routine which showed a MsgBox, would you expect ExitApp to kick in after 3 seconds and terminate the script regardless? Even though the thread which called ExitApp is suspended? What if the destructors are performing important cleanup work that sometimes takes longer than 3 seconds?

__Delete cannot propagate exceptions up the stack, because it is invoked by the reference counting mechanism (Release), not by the script. Even if it could, it wouldn't make sense to do so. __Delete is called in all sorts of situations, including times when the stack is already unwinding due to return, Exit or an exception. If __Delete propagated exceptions and try ExitApp worked, it would be the opposite of what you want. It would ignore the error and continue the script (having already destroyed some of the global/static objects), instead of finishing the destruction of global/static objects and terminating the script.

__Delete should not throw an exception. Do not suppress the error - fix your destructor to not throw it.

TASKKILL
There's no need to rely on an external executable file; you can call DllCall("ExitProcess", "uint", ExitCode).

However, I won't be using either. It would skip a lot of other cleanup, like removing the tray icon, unregistering the keyboard/mouse hooks and non-hook hotkeys, unregistering the clipboard listener (OnClipboardChange), destroying GDI objects and GUI windows, menus, etc. At least some of that stuff is probably beneficial.

Imagine being unable to shutdown your computer because you have 5 unsaved notepad windows.
Why imagine? Have you even used Windows? :HeHe:

Perhaps ExitApp Force is more felicitous.
Force is a valid variable name for an exit code.
iseahound
Posts: 696
Joined: 13 Aug 2016, 21:04
GitHub: iseahound

Re: v1 & v2 When the class name is greater than the variable assigned to the instance, __Delete cannot get static class

Post by iseahound » 06 May 2021, 13:53

Does an internal ID for all objects exist within AutoHotkey? That way on script exit, they can be unloaded reverse chronologically, as in reverse order of creation. As an added bonus, maybe something similar to Python's id() function would exist, as I assume that since primitives derive from any, everything is an object.
Post Reply

Return to “Bug Reports”