xsharp.eu • ThreadLocal issue when closing application
Page 1 of 1

ThreadLocal issue when closing application

Posted: Thu Jun 17, 2021 7:53 am
by leon-ts
Hi,

There is a class A, in the destructor of which some function is called, which in turn uses the AScan function. The application (MDI) creates an object (instance) of class A in one of its windows and does not explicitly destroy it (this is not necessary). This object will be destroyed when GC is called. When the application (MDI) is closed, the destructor is called on the instance of class A. Then it comes down to the AScan function, and it throws an ObjectDisposedException with a ThreadLocal object specified. As I understand it, ThreadLocal is used in RuntimeState.

Why is the ThreadLocal disposed before there are any objects that have not yet been destroyed that can use it?

P.S. The problem occurs in a real application. I tried to create a situation in a small test application, but the problem did not appear there.

Best regards,
Leonid

ThreadLocal issue when closing application

Posted: Sat Jun 19, 2021 4:58 pm
by Chris
Hi Leonid,

hard to tell, without seeing the code. What is the exact excception message that you get? Also can you please show the code of the destructor?

ThreadLocal issue when closing application

Posted: Sat Jun 19, 2021 5:06 pm
by robert
Leonid
If you want TO clean up before the local storage is gone then I recommend that you create an exit procedure:

Procedure SomeName EXIT
// clean up here

The compiler creates a special function that calls all exit procedures at app shutdown when the runtime is still intact.

Robert

ThreadLocal issue when closing application

Posted: Mon Jun 21, 2021 8:52 am
by leon-ts
Hi,

The problem comes from the fact that in my application one of the classes had a bad finalizer design. I will not give a real example of the code because of its volume. This code was written in VO to support event handling. It was later transferred to X#. But for this discussion, I wrote an example that matches it:

Code: Select all

STATIC GLOBAL __aListeners := {} AS ARRAY

FUNCTION AddListener(oListener AS MyListenerClass) AS VOID
	
	LOCAL i AS DWORD
	IF ( i := AScan( __aListeners, oListener ) ) == 0
		__aListeners:Add(oListener)
	ENDIF
	
	RETURN

FUNCTION RemoveListener(oListener AS MyListenerClass) AS VOID

	LOCAL i AS DWORD
	IF ( i := AScan( __aListeners, oListener ) ) > 0
		__aListeners:Delete(i)
		__aListeners:Resize( __aListeners:Length - 1 )
	ENDIF

	RETURN

CLASS MyClass

	HIDDEN oListener AS MyListenerClass

	CONSTRUCTOR()
		oListener := MyListenerClass{SELF}
		AddListener(oListener)
		RETURN

	DESTRUCTOR()
		RemoveListener(oListener)
		RETURN

	// MyClass methods and properties
	// ...

END CLASS

CLASS MyListenerClass

	HIDDEN oOwner AS OBJECT

	CONSTRUCTOR( oOwner_ AS OBJECT )
		SELF:oOwner := oOwner_
		RETURN

	// MyListenerClass methods and properties
	// ...

END CLASS
The problem is that the implicit finalization of the MyClass is never called. This is due to the fact that the global __aListeners array stores a reference to an instance of the MyListenerClass class, which in turn stores a reference to MyClass in the oOwner field. As a result, the instance of the class MyClass is designated in the GC as "alive" and the destructor of the class MyClass is not called. And only when the application closes and the finalizers of all objects are called, then the destructor code presented above is executed. But at this point (in a real application, not in this example), the ThreadLocal object has already been destroyed and an exception is thrown on the AScan function:
Exception Unhandled: System.ObjectDisposedException: Cannot access a disposed object. Object name: ThreadLocal.
Now I have added an implementation from IDisposable to the MyClass class and implemented the Dispose method, which deregisters the oListener object. And added code to the application that explicitly calls the Dispose method on an instance of the MyClass class. Now the problem does not arise. But the question still remains open, because the problem with the design of the code in my example only revealed the problem with ThreadLocal: when object finalizers are executed when the application is closed, it may be impossible to call functions such as AScan in them.

Best regards,
Leonid

ThreadLocal issue when closing application

Posted: Mon Jun 21, 2021 11:40 am
by Chris
Hi Leonid,

I think I see what's happening. AScan() internally calls SetExact() to use this setting when scanning for strings and SetExact() in turn accesses a ThreadLocal<RuntimeState> object, which apparently has been already disposed when the destructor of your object is called.

There's actually a safeguard mechanism when reading this object, with the code

Code: Select all

CLASS RuntimeState
PRIVATE STATIC _shutdown := FALSE AS LOGIC  // To prevent creating state when shutting down	PUBLIC 
STATIC METHOD GetValue<T> (nSetting AS XSharp.Set) AS T
        IF _shutdown
            // There is no RuntimeState when shutting down
            RETURN Default(T)
        ENDIF
	RETURN currentState:Value:_GetThreadValue<T>(nSetting);
so normally SetExact() should simply return FALSE on app shutdown, but I see that the _shutdown field never becomes TRUE in the runtime code. Robert, I guess you just forgot to assign this?

ThreadLocal issue when closing application

Posted: Mon Jun 21, 2021 12:46 pm
by leon-ts
Hi Chris,
Thank you for the clarification!

Best regards,
Leonid