[NUI] Fix Registry's memory leak (#2099)
authordongsug-song <35130733+dongsug-song@users.noreply.github.com>
Mon, 19 Oct 2020 05:07:56 +0000 (14:07 +0900)
committerGitHub <noreply@github.com>
Mon, 19 Oct 2020 05:07:56 +0000 (14:07 +0900)
- new RefObject(xxx, false), this causes unhandled RefObject in dali.
- "cMemoryOwn = false" means that this doesn't care about dali native's object and will not delete native object when nui dispose.
- currently "ViewAddedEventToWindow" is not used and this will be fixed by using Event subscribing pattern soon.
- remove "internal BaseHandle.GetObjectPtr()" method which is not used anymore.

src/Tizen.NUI/src/internal/Registry.cs
src/Tizen.NUI/src/public/BaseComponents/View.cs
src/Tizen.NUI/src/public/BaseComponents/ViewInternal.cs
src/Tizen.NUI/src/public/BaseHandle.cs
test/Tizen.NUI.Tests/Tizen.NUI.TCT/Tizen.NUI.Components.Tests.sln [deleted file]
test/Tizen.NUI.Tests/Tizen.NUI.TCT/testcase/ViewMemoryLeakTest.cs [new file with mode: 0755]

index 18e0e93..a4941bf 100755 (executable)
@@ -58,8 +58,7 @@ namespace Tizen.NUI
         internal static void Register(BaseHandle baseHandle)
         {
             // We store a pointer to the RefObject for the control
-            RefObject refObj = baseHandle.GetObjectPtr();
-            IntPtr refCptr = (IntPtr)RefObject.getCPtr(refObj);
+            IntPtr refCptr = Interop.BaseHandle.BaseHandle_GetObjectPtr(baseHandle.GetBaseHandleCPtrHandleRef);
 
             RegistryCurrentThreadCheck();
 
@@ -77,8 +76,7 @@ namespace Tizen.NUI
         /// <param name="baseHandle"> The instance of BaseHandle (C# base class)</param>
         internal static void Unregister(BaseHandle baseHandle)
         {
-            RefObject refObj = baseHandle.GetObjectPtr();
-            IntPtr refCptr = (IntPtr)RefObject.getCPtr(refObj);
+            IntPtr refCptr = Interop.BaseHandle.BaseHandle_GetObjectPtr(baseHandle.GetBaseHandleCPtrHandleRef);
 
             RegistryCurrentThreadCheck();
             WeakReference refe;
@@ -92,8 +90,7 @@ namespace Tizen.NUI
 
         internal static BaseHandle GetManagedBaseHandleFromNativePtr(BaseHandle baseHandle)
         {
-            RefObject refObj = baseHandle.GetObjectPtr();
-            IntPtr refObjectPtr = (IntPtr)RefObject.getCPtr(refObj);
+            IntPtr refObjectPtr = Interop.BaseHandle.BaseHandle_GetObjectPtr(baseHandle.GetBaseHandleCPtrHandleRef);
 
             // we store a dictionary of ref-obects (C++ land) to managed obects (C# land)
             return GetManagedBaseHandleFromRefObject(refObjectPtr);
index 74382b4..1e98532 100755 (executable)
@@ -133,8 +133,9 @@ namespace Tizen.NUI.BaseComponents
                 PositionUsesPivotPoint = false;
             }
 
-            _onWindowSendEventCallback = SendViewAddedEventToWindow;
-            this.OnWindowSignal().Connect(_onWindowSendEventCallback);
+            //ToDo: this has memory leak and this is not used currently. will be fixed soon by using Event subscribing pattern.
+            //_onWindowSendEventCallback = SendViewAddedEventToWindow;
+            //this.OnWindowSignal().Connect(_onWindowSendEventCallback);
 
             if (!shown)
             {
index 5aca0fb..cf42ffa 100755 (executable)
@@ -1081,16 +1081,6 @@ namespace Tizen.NUI.BaseComponents
                 DisConnectFromSignals();
             }
 
-            if (swigCPtr.Handle != global::System.IntPtr.Zero)
-            {
-                if (swigCMemOwn)
-                {
-                    swigCMemOwn = false;
-                    Interop.View.delete_View(swigCPtr);
-                }
-                swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
-            }
-
             foreach (View view in Children)
             {
                 view.InternalParent = null;
index 7c4fd48..5ccca2c 100755 (executable)
@@ -442,14 +442,6 @@ namespace Tizen.NUI
             PropertySet?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
 
-        internal RefObject GetObjectPtr()
-        {
-            global::System.IntPtr cPtr = Interop.BaseHandle.BaseHandle_GetObjectPtr(swigCPtrCopy);
-            RefObject ret = (cPtr == global::System.IntPtr.Zero) ? null : new RefObject(cPtr, false);
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
-            return ret;
-        }
-
         /// <summary>
         /// Dispose.
         /// </summary>
diff --git a/test/Tizen.NUI.Tests/Tizen.NUI.TCT/Tizen.NUI.Components.Tests.sln b/test/Tizen.NUI.Tests/Tizen.NUI.TCT/Tizen.NUI.Components.Tests.sln
deleted file mode 100755 (executable)
index 6e5fd37..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26124.0
-MinimumVisualStudioVersion = 15.0.26124.0
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tizen.NUI.Components.Tests", "Tizen.NUI.Components.Tests.csproj", "{EDA7CE08-63F0-4D30-A8DC-000A32C39119}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunit.framework", "..\nunit.framework\nunit.framework.csproj", "{C3B4E786-5F63-4253-ABC5-DB2268FF3278}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "nunitlite", "..\nunitlite\nunitlite.csproj", "{1EE295D5-6D47-4279-89B2-3095EF49628B}"
-EndProject
-Global
-       GlobalSection(SolutionConfigurationPlatforms) = preSolution
-               Debug|Any CPU = Debug|Any CPU
-               Debug|x64 = Debug|x64
-               Debug|x86 = Debug|x86
-               Release|Any CPU = Release|Any CPU
-               Release|x64 = Release|x64
-               Release|x86 = Release|x86
-       EndGlobalSection
-       GlobalSection(ProjectConfigurationPlatforms) = postSolution
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Debug|Any CPU.Build.0 = Debug|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Debug|x64.ActiveCfg = Debug|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Debug|x64.Build.0 = Debug|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Debug|x86.ActiveCfg = Debug|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Debug|x86.Build.0 = Debug|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Release|Any CPU.ActiveCfg = Release|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Release|Any CPU.Build.0 = Release|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Release|x64.ActiveCfg = Release|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Release|x64.Build.0 = Release|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Release|x86.ActiveCfg = Release|Any CPU
-               {EDA7CE08-63F0-4D30-A8DC-000A32C39119}.Release|x86.Build.0 = Release|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Debug|Any CPU.Build.0 = Debug|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Debug|x64.ActiveCfg = Debug|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Debug|x64.Build.0 = Debug|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Debug|x86.ActiveCfg = Debug|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Debug|x86.Build.0 = Debug|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Release|Any CPU.ActiveCfg = Release|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Release|Any CPU.Build.0 = Release|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Release|x64.ActiveCfg = Release|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Release|x64.Build.0 = Release|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Release|x86.ActiveCfg = Release|Any CPU
-               {C3B4E786-5F63-4253-ABC5-DB2268FF3278}.Release|x86.Build.0 = Release|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Debug|Any CPU.Build.0 = Debug|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Debug|x64.ActiveCfg = Debug|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Debug|x64.Build.0 = Debug|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Debug|x86.ActiveCfg = Debug|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Debug|x86.Build.0 = Debug|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Release|Any CPU.ActiveCfg = Release|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Release|Any CPU.Build.0 = Release|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Release|x64.ActiveCfg = Release|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Release|x64.Build.0 = Release|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Release|x86.ActiveCfg = Release|Any CPU
-               {1EE295D5-6D47-4279-89B2-3095EF49628B}.Release|x86.Build.0 = Release|Any CPU
-       EndGlobalSection
-       GlobalSection(SolutionProperties) = preSolution
-               HideSolutionNode = FALSE
-       EndGlobalSection
-       GlobalSection(ExtensibilityGlobals) = postSolution
-               SolutionGuid = {B322CDDE-6430-4B68-AD4B-17E5761E3C20}
-       EndGlobalSection
-EndGlobal
diff --git a/test/Tizen.NUI.Tests/Tizen.NUI.TCT/testcase/ViewMemoryLeakTest.cs b/test/Tizen.NUI.Tests/Tizen.NUI.TCT/testcase/ViewMemoryLeakTest.cs
new file mode 100755 (executable)
index 0000000..c6f0123
--- /dev/null
@@ -0,0 +1,281 @@
+
+using NUnit.Framework;
+using NUnit.Framework.TUnit;
+using System;
+using Tizen.NUI.Components;
+using Tizen.NUI.BaseComponents;
+using global::System.Resources;
+
+namespace Tizen.NUI.Devel.Tests
+{
+    [TestFixture]
+    [Description("View memory leak Tests")]
+    public class ViewMemoryLeakTests
+    {
+        private const string TAG = "NUITEST";
+
+        [SetUp]
+        public void Init()
+        {
+        }
+
+        [TearDown]
+        public void Destroy()
+        {
+        }
+
+        [Test]
+        [Category("P1")]
+        [Description("view memory leak test")]
+        [Property("SPEC", "local test")]
+        [Property("SPEC_URL", "-")]
+        [Property("CRITERIA", "local test")]
+        [Property("COVPARAM", "")]
+        [Property("AUTHOR", "dongsug.song")]
+        public void ViewMemoryLeakTest()
+        {
+            /* TEST CODE */
+            test1();
+        }
+
+        private View rootView;
+        private Timer timer;
+        private const uint numberOfTestCount = 1000;
+        private const uint numberOfViews = 500;
+        private int currentTestCount = 0;
+
+        private TextLabel doGC, add500views, remove500views;
+        void test1()
+        {
+            rootView = new View();
+            rootView.Size2D = new Size2D(100, 100);
+            rootView.Position2D = new Position2D(70, 70);
+            rootView.Focusable = true;
+            rootView.BackgroundColor = Color.Red;
+            rootView.KeyEvent += RootView_KeyEvent;
+            rootView.TouchEvent += RootView_TouchEvent;
+
+            Window.Instance.GetDefaultLayer().Add(rootView);
+
+            FocusManager.Instance.SetCurrentFocusView(rootView);
+
+            CreateViews();
+
+            timer = new Timer(1000);
+            timer.Tick += Timer_Tick;
+
+            doGC = new TextLabel()
+            {
+                Size = new Size(200, 100),
+                Position = new Position(500, 500),
+                Text = "Do GC!",
+                BackgroundColor = Color.White,
+            };
+            doGC.TouchEvent += DoGC_TouchEvent;
+            Window.Instance.GetDefaultLayer().Add(doGC);
+
+            add500views = new TextLabel()
+            {
+                Size = new Size(200, 100),
+                Position = new Position(500, 700),
+                Text = "add500views",
+                BackgroundColor = Color.White,
+            };
+            add500views.TouchEvent += add500views_TouchEvent;
+            Window.Instance.GetDefaultLayer().Add(add500views);
+
+            remove500views = new TextLabel()
+            {
+                Size = new Size(200, 100),
+                Position = new Position(500, 900),
+                Text = "remove500views",
+                BackgroundColor = Color.White,
+            };
+            remove500views.TouchEvent += remove500views_TouchEvent;
+            Window.Instance.GetDefaultLayer().Add(remove500views);
+        }
+
+        private bool remove500views_TouchEvent(object source, View.TouchEventArgs e)
+        {
+            if (e.Touch.GetState(0) == PointStateType.Down)
+            {
+                Tizen.Log.Debug("NUITEST", "remove500views");
+                DestroyViews();
+                var me = source as TextLabel;
+                me.BackgroundColor = Color.Blue;
+            }
+            else
+            {
+                var me = source as TextLabel;
+                me.BackgroundColor = Color.White;
+            }
+            return true;
+        }
+
+        private bool add500views_TouchEvent(object source, View.TouchEventArgs e)
+        {
+            if (e.Touch.GetState(0) == PointStateType.Down)
+            {
+                Tizen.Log.Debug("NUITEST", "add500views");
+                CreateViews();
+                var me = source as TextLabel;
+                me.BackgroundColor = Color.Red;
+            }
+            else
+            {
+                var me = source as TextLabel;
+                me.BackgroundColor = Color.White;
+            }
+            return true;
+        }
+
+        private bool DoGC_TouchEvent(object source, View.TouchEventArgs e)
+        {
+            if (e.Touch.GetState(0) == PointStateType.Down)
+            {
+                Tizen.Log.Debug("NUITEST", "Do GC!");
+                GC.Collect();
+                GC.WaitForPendingFinalizers();
+
+                var me = source as TextLabel;
+                me.BackgroundColor = Color.Red;
+            }
+            else
+            {
+                var me = source as TextLabel;
+                me.BackgroundColor = Color.White;
+            }
+            return true;
+        }
+
+        bool onceFlag = false;
+        private bool RootView_TouchEvent(object source, View.TouchEventArgs e)
+        {
+            if (e.Touch.GetState(0) == PointStateType.Down)
+            {
+                if (onceFlag == false)
+                {
+                    onceFlag = true;
+                    timer.Start();
+
+                    Tizen.Log.Debug("NUITEST", "TEST IS STARTED");
+                }
+            }
+            return true;
+        }
+
+        private bool RootView_KeyEvent(object source, View.KeyEventArgs e)
+        {
+            if (e.Key.State == Key.StateType.Down)
+            {
+                if (e.Key.KeyPressedName == "Return")
+                {
+                    timer.Start();
+
+                    Tizen.Log.Debug("NUITEST", $"TEST IS STARTED rootView's child cnt={rootView.ChildCount}");
+                }
+            }
+
+            return false;
+        }
+
+        private bool Timer_Tick(object source, Timer.TickEventArgs e)
+        {
+            GC.Collect();
+            GC.WaitForPendingFinalizers();
+
+            if (currentTestCount == numberOfTestCount)
+            {
+                Tizen.Log.Debug("NUITEST", $"TEST IS FINISHED. rootView's child cnt={rootView.ChildCount}");
+                onceFlag = false;
+                currentTestCount = 0;
+                return false;
+            }
+
+            if (currentTestCount % 2 == 0)
+            {
+                DestroyViews();
+            }
+            else
+            {
+                CreateViews();
+            }
+
+            currentTestCount++;
+
+            return true;
+        }
+
+        View[] viewArray = new View[numberOfViews];
+        bool workingFlag = false;
+        private void CreateViews()
+        {
+            Tizen.Log.Debug("NUITEST", $"CreateViews() start, numberOfViews={numberOfViews}, rootView's child cnt={rootView.ChildCount}");
+            if (workingFlag)
+            {
+                Tizen.Log.Debug("NUITEST", $"@@err CreateViews() working now! just return!");
+                return;
+            }
+            workingFlag = true;
+
+            for (uint i = 0; i < numberOfViews; i++)
+            {
+                View v = new View()
+                {
+                    Size = new Size(20, 20),
+                    BackgroundColor = Color.Green
+                };
+                rootView.Add(v);
+
+                //check ref count
+                //viewArray[i] = v;
+                //RefObject ro = v.GetObjectPtr();
+                //RefObject rv = rootView.GetObjectPtr();
+                //Tizen.Log.Debug("NUITEST", $"1) child.count={ro.ReferenceCount()}, parent cnt={rv.ReferenceCount()}");
+            }
+            Tizen.Log.Debug("NUITEST", $"CreateViews() end rootView's child cnt={rootView.ChildCount}\n");
+            workingFlag = false;
+        }
+
+        private void DestroyViews()
+        {
+            if (workingFlag)
+            {
+                Tizen.Log.Debug("NUITEST", $"@@err DestroyViews() working now! just return!");
+                return;
+            }
+            workingFlag = true;
+
+            if (rootView != null)
+            {
+                if (rootView.ChildCount > 0)
+                {
+                    Tizen.Log.Debug("NUITEST", $"DestroyViews() start, rootView.ChildCount={rootView.ChildCount}");
+                    for (int i = (int)rootView.ChildCount - 1; i >= 0; i--)
+                    {
+                        View v = rootView.GetChildAt((uint)i);
+                        v.Unparent();
+                        v.Dispose();
+                    }
+                    Tizen.Log.Debug("NUITEST", $"DestroyViews() end, rootView.ChildCount={rootView.ChildCount} \n");
+                }
+                //check ref count
+                //for (uint i = 0; i < numberOfViews; i++)
+                //{
+                //    View v = viewArray[i];
+                //    if(v != null)
+                //    {
+                //        RefObject ro = v.GetObjectPtr();
+                //        RefObject rv = rootView.GetObjectPtr();
+
+                //        Tizen.Log.Debug("NUITEST", $"1) child.count={ro.ReferenceCount()}, parent cnt={rv.ReferenceCount()}");
+
+                //        v.Dispose();
+                //        viewArray[i] = v = null;
+                //    }
+                //}
+            }
+            workingFlag = false;
+        }
+    }
+}