[NUI] fix testhub fail, Add Dispose() in BaseHandle.Reset() where the body was delete...
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Common / BaseHandle.cs
index f68edba..f221e0a 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright(c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright(c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ using System;
 using System.ComponentModel;
 using System.Runtime.CompilerServices;
 using Tizen.NUI.Binding;
+using global::System.Diagnostics;
 
 namespace Tizen.NUI
 {
@@ -31,15 +32,15 @@ namespace Tizen.NUI
         /// swigCMemOwn
         /// </summary>
         /// <since_tizen> 3 </since_tizen>
-        [Obsolete("Deprecated in API9, Will be removed in API11, Please use SwigCMemOwn")]
+        [Obsolete("Deprecated in API9, will be removed in API11, Use SwigCMemOwn")]
         [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "<Pending>")]
         protected bool swigCMemOwn;
 
         /// <summary>
-        /// A flag to check if it is already disposed.
+        /// The flag to check if it is already disposed of.
         /// </summary>
         /// <since_tizen> 3 </since_tizen>
-        [Obsolete("Deprecated in API9, Will be removed in API11, Please use Disposed")]
+        [Obsolete("Deprecated in API9, will be removed in API11, Use Disposed")]
         [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "<Pending>")]
         protected bool disposed = false;
 
@@ -47,7 +48,7 @@ namespace Tizen.NUI
         private global::System.Runtime.InteropServices.HandleRef swigCPtrCopy;
         private bool registerMe;
 
-        //A Flag to check who called Dispose(). (By User or DisposeQueue)
+        //The flag to check who called Dispose(). (By User or DisposeQueue)
         private bool isDisposeQueued = false;
 
         /// <summary>
@@ -74,6 +75,8 @@ namespace Tizen.NUI
             //to catch derived classes dali native exceptions
             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
 
+            DebugFileLogging.Instance.WriteLog($"BaseHandle.contructor with cMemeryOwn:{cMemoryOwn} START");
+
             registerMe = swigCMemOwn = cMemoryOwn;
             swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
             // using copy constructor to create another native handle so Registry.Unregister works fine.
@@ -82,14 +85,19 @@ namespace Tizen.NUI
 
             if (registerMe)
             {
-
                 // Register this instance of BaseHandle in the registry.
                 Registry.Register(this);
             }
+
+            disposeDebuggingCtor();
+            DebugFileLogging.Instance.WriteLog($" BaseHandle.contructor with cMemeryOwn END");
+            DebugFileLogging.Instance.WriteLog($"=============================");
         }
 
         internal BaseHandle(global::System.IntPtr cPtr)
         {
+            DebugFileLogging.Instance.WriteLog($"BaseHandle.contructor START");
+
             registerMe = swigCMemOwn = true;
 
             swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
@@ -103,12 +111,17 @@ namespace Tizen.NUI
                 // Register this instance of BaseHandle in the registry.
                 Registry.Register(this);
             }
+
+            disposeDebuggingCtor();
+            DebugFileLogging.Instance.WriteLog($"BaseHandle.contructor END");
+            DebugFileLogging.Instance.WriteLog($"=============================");
         }
 
         /// <summary>
         /// Dispose.
         /// </summary>
         /// <since_tizen> 3 </since_tizen>
+        // following this guide: https://docs.microsoft.com/ko-kr/dotnet/fundamentals/code-analysis/quality-rules/ca1063?view=vs-2019 (CA1063)
         ~BaseHandle() => Dispose(false);
 
         /// <summary>
@@ -116,7 +129,7 @@ namespace Tizen.NUI
         /// </summary>
         /// <since_tizen> 5 </since_tizen>
         /// <seealso cref="BindableObject.PropertyChanged"/>
-        [Obsolete("Deprecated in API9, Will be removed in API11, Please use BindableObject.PropertyChanged instead!")]
+        [Obsolete("Deprecated in API9, will be removed in API11, Use BindableObject.PropertyChanged instead.")]
         public event PropertyChangedEventHandler PropertySet;
 
         internal global::System.Runtime.InteropServices.HandleRef GetBaseHandleCPtrHandleRef
@@ -292,8 +305,15 @@ namespace Tizen.NUI
         /// <since_tizen> 3 </since_tizen>
         public void Dispose()
         {
-            Dispose(true);
-            System.GC.SuppressFinalize(this);
+            if (isDisposeQueued)
+            {
+                Dispose(DisposeTypes.Implicit);
+            }
+            else
+            {
+                Dispose(true);
+            }
+            GC.SuppressFinalize(this);
         }
 
         /// <summary>
@@ -322,11 +342,29 @@ namespace Tizen.NUI
                 //Throw exception if Dispose() is called in separate thread.
                 if (!Window.IsInstalled())
                 {
-                    throw new System.InvalidOperationException("This API called from separate thread. This API must be called from MainThread.");
+                    var process = global::System.Diagnostics.Process.GetCurrentProcess().Id;
+                    var thread = global::System.Threading.Thread.CurrentThread.ManagedThreadId;
+                    var me = this.GetType().FullName;
+
+                    DebugFileLogging.Instance.WriteLog("[NUI][BaseHandle] This API called from separate thread. This API must be called from MainThread. \n" +
+                        $" process:{process} thread:{thread}, disposing:{disposing}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me}\n");
+
+                    Tizen.Log.Fatal("NUI", $"[NUI][BaseHandle] This API called from separate thread. This API must be called from MainThread. \n" +
+                        $" process:{process} thread:{thread}, disposing:{disposing}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me}\n");
+
+                    //to fix ArtApp black screen issue. this will be enabled after talking with ArtApp team and fixing it.
+                    // throw new global::System.InvalidOperationException("[NUI][BaseHandle] This API called from separate thread. This API must be called from MainThread. \n" +
+                    //     $" process:{process} thread:{thread}, disposing:{disposing}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me}\n");
                 }
 
                 if (isDisposeQueued)
                 {
+                    DebugFileLogging.Instance.WriteLog($"should not be here! (dead code) this will be removed!");
+
+                    Tizen.Log.Fatal("NUI", $"[NUI] should not be here! (dead code) this will be removed!");
+
+                    //to fix ArtApp black screen issue. this will be enabled after talking with ArtApp team and fixing it.
+                    // throw new global::System.Exception($"[NUI] should not be here! (dead code) this will be removed!");
                     Dispose(DisposeTypes.Implicit);
                 }
                 else
@@ -394,10 +432,13 @@ namespace Tizen.NUI
         /// Resets the handle.
         /// </summary>
         /// <since_tizen> 3 </since_tizen>
+        /// <remark>
+        /// This will be deprecated, please use Dispose() instead.
+        /// </remark>
         public void Reset()
         {
-            Interop.BaseHandle.Reset(swigCPtrCopy);
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+            this.Dispose();
+            NUILog.Error("[ERROR] This(BaseHandle.Reset) will be deprecated, please use Dispose() instead!");
         }
 
         /// <summary>
@@ -455,7 +496,7 @@ namespace Tizen.NUI
         /// <since_tizen> 3 </since_tizen>
         public bool IsEqual(BaseHandle rhs)
         {
-            if (disposed == true)
+            if (disposed == true || rhs == null || !rhs.HasBody())
             {
                 return false;
             }
@@ -486,6 +527,8 @@ namespace Tizen.NUI
                 return;
             }
 
+            DebugFileLogging.Instance.WriteLog($"BaseHandle.Dispose({type}) START");
+
             if (type == DisposeTypes.Explicit)
             {
                 //Called by User
@@ -504,6 +547,8 @@ namespace Tizen.NUI
                 Registry.Unregister(this);
             }
 
+            disposeDebuggingDispose(type);
+
             if (SwigCPtr.Handle != IntPtr.Zero)
             {
                 if (swigCMemOwn)
@@ -513,14 +558,31 @@ namespace Tizen.NUI
                 }
                 swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
             }
+            else
+            {
+                var me = this.GetType().FullName;
+                DebugFileLogging.Instance.WriteLog($"[ERR] SwigCPtr is NULL, need to check! me:{me}");
+                Log.Error("NUI", $"[ERR] SwigCPtr is NULL, need to check! me:{me}");
+            }
+
             if (swigCPtrCopy.Handle != global::System.IntPtr.Zero)
             {
                 swigCMemOwn = false;
                 Interop.BaseHandle.DeleteBaseHandle(swigCPtrCopy);
                 swigCPtrCopy = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
             }
+            else
+            {
+                var me = this.GetType().FullName;
+                DebugFileLogging.Instance.WriteLog($"[ERR] swigCPtrCopy is NULL, need to check! me:{me}");
+                Log.Error("NUI", $"[ERR] swigCPtrCopy is NULL, need to check! me:{me}");
+            }
 
             disposed = true;
+            DebugFileLogging.Instance.WriteLog($"BaseHandle.Dispose({type}) END");
+            DebugFileLogging.Instance.WriteLog($"=============================");
+            NUILog.Debug($"BaseHandle.Dispose({type}) END");
+            NUILog.Debug($"=============================");
         }
 
         /// <summary>
@@ -553,13 +615,41 @@ namespace Tizen.NUI
 
         internal global::System.Runtime.InteropServices.HandleRef SwigCPtr
         {
-            get => swigCPtr;
-            set
+            get
             {
-                swigCPtr = value;
+                if (swigCPtr.Handle == IntPtr.Zero)
+                {
+                    var process = global::System.Diagnostics.Process.GetCurrentProcess().Id;
+                    var thread = global::System.Threading.Thread.CurrentThread.ManagedThreadId;
+                    var me = this.GetType().FullName;
+
+                    Tizen.Log.Fatal("NUI", $"SwigCPtr Error! NUI's native dali object is already disposed. " +
+                        $"OR the native dali object handle of NUI becomes null! \n" +
+                        $" process:{process} thread:{thread}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me}\n");
+
+                    Tizen.Log.Fatal("NUI", $"[ERROR] back trace!");
+                    global::System.Diagnostics.StackTrace st = new global::System.Diagnostics.StackTrace(true);
+                    for (int i = 0; i < st.FrameCount; i++)
+                    {
+                        global::System.Diagnostics.StackFrame sf = st.GetFrame(i);
+                        Tizen.Log.Fatal("NUI", " Method " + sf.GetMethod());
+                    }
+                    Tizen.Log.Fatal("NUI", "Error! just return here with null swigCPtr! this can cause unknown error or crash in next step");
+
+                    //to fix ArtApp black screen issue. this will be enabled after talking with ArtApp team and fixing it.
+                    // throw new ObjectDisposedException(nameof(SwigCPtr), $"Error! NUI's native dali object is already disposed. " +
+                    //     $"OR the native dali object handle of NUI becomes null! \n" +
+                    //     $" process:{process} thread:{thread}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me}\n");
+                }
+                return swigCPtr;
             }
         }
 
+        internal bool IsNativeHandleInvalid()
+        {
+            return swigCPtr.Handle == IntPtr.Zero;
+        }
+
         /// <summary>
         /// swigCMemOwn
         /// </summary>
@@ -567,9 +657,69 @@ namespace Tizen.NUI
         protected internal bool SwigCMemOwn => swigCMemOwn;
 
         /// <summary>
-        /// A flag to check if it is already disposed.
+        /// The flag to check if it is already disposed of.
         /// </summary>
         [EditorBrowsable(EditorBrowsableState.Never)]
         protected internal bool Disposed => disposed;
+
+        /// <summary>
+        /// The flag to check if it is disposed by DisposeQueue.
+        /// </summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        protected internal bool IsDisposeQueued => isDisposeQueued;
+
+        [Conditional("NUI_DISPOSE_DEBUG_ON")]
+        private void disposeDebuggingCtor()
+        {
+            DebugFileLogging.Instance.WriteLog($"type:{GetType()} copyNativeHandle:{swigCPtrCopy.Handle.ToString("X8")}");
+            if (this is BaseComponents.View view)
+            {
+                DebugFileLogging.Instance.WriteLog($"ID:{view.ID} Name:{view.Name}");
+                //back trace
+                global::System.Diagnostics.StackTrace st = new global::System.Diagnostics.StackTrace(true);
+                for (int i = 0; i < st.FrameCount; i++)
+                {
+                    global::System.Diagnostics.StackFrame sf = st.GetFrame(i);
+                    DebugFileLogging.Instance.WriteLog($"[{i}] {sf.GetMethod()}");
+                }
+            }
+        }
+
+        [Conditional("NUI_DISPOSE_DEBUG_ON")]
+        private void disposeDebuggingDispose(DisposeTypes type)
+        {
+            DebugFileLogging.Instance.WriteLog($"swigCMemOwn:{swigCMemOwn} type:{GetType()} copyNativeHandle:{swigCPtrCopy.Handle.ToString("X8")} HasBody:{HasBody()}");
+
+            if (swigCPtr.Handle != IntPtr.Zero && swigCPtrCopy.Handle != IntPtr.Zero && HasBody())
+            {
+                using var currentProcess = global::System.Diagnostics.Process.GetCurrentProcess();
+                var process = currentProcess.Id;
+                var thread = global::System.Threading.Thread.CurrentThread.ManagedThreadId;
+                var me = this.GetType().FullName;
+                var daliId = "unknown";
+                var hash = this.GetType().GetHashCode();
+                var name = "unknown";
+
+                if (this is BaseComponents.View)
+                {
+                    daliId = Interop.Actor.GetId(swigCPtrCopy).ToString();
+                    name = Interop.Actor.GetName(swigCPtrCopy);
+                    BaseObject tmp = new BaseObject(Interop.BaseHandle.GetBaseObject(swigCPtrCopy), false);
+                    var refCnt = tmp.ReferenceCount();
+                    tmp.Dispose();
+                    if (refCnt > 2)
+                    {
+                        DebugFileLogging.Instance.WriteLog($"[ERR] reference count is over than 2. Could be a memory leak. Need to check! \n" +
+                            $" process:{process} thread:{thread}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me} \n" +
+                            $" disposeType:{type}, name:{name}, daliID:{daliId}, hash:{hash}, refCnt:{refCnt}");
+
+                        Log.Error("NUI", $"[ERR] reference count is over than 2. Could be a memory leak. Need to check! \n" +
+                            $" process:{process} thread:{thread}, isDisposed:{this.disposed}, isDisposeQueued:{this.isDisposeQueued}, me:{me} \n" +
+                            $" disposeType:{type}, name:{name}, daliID:{daliId}, hash:{hash}, refCnt:{refCnt}");
+                    }
+                }
+            }
+        }
+
     }
 }