[NUI] Dispose queue incrementally
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Common / DisposeQueue.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.Diagnostics.CodeAnalysis;
21 using System.Linq;
22
23 namespace Tizen.NUI
24 {
25     [SuppressMessage("Microsoft.Design", "CA1001:Types that own disposable fields should be disposable", Justification = "This is a singleton class and is not disposed")]
26     internal class DisposeQueue
27     {
28         private static readonly DisposeQueue disposableQueue = new DisposeQueue();
29         private List<IDisposable> disposables = new List<IDisposable>();
30         private System.Object listLock = new object();
31
32         // Dispose incrementally at least max(100, incrementallyDisposedQueue * 20%) for each 1 event callback
33         private const long minimumIncrementalCount = 100;
34         private const long minimumIncrementalRate = 20;
35         private List<IDisposable> incrementallyDisposedQueue = new List<IDisposable>();
36
37         private EventThreadCallback eventThreadCallback;
38         private EventThreadCallback.CallbackDelegate disposeQueueProcessDisposablesDelegate;
39
40         private bool initialied = false;
41         private bool processorRegistered = false;
42         private bool eventThreadCallbackTriggered = false;
43
44         private bool incrementalDisposeSupported = false;
45         private bool fullCollectRequested = false;
46
47         private DisposeQueue()
48         {
49         }
50
51         ~DisposeQueue()
52         {
53             Tizen.Log.Debug("NUI", $"DisposeQueue is destroyed\n");
54             initialied = false;
55         }
56
57         public static DisposeQueue Instance
58         {
59             get { return disposableQueue; }
60         }
61
62         public bool IncrementalDisposeSupported
63         {
64             get => incrementalDisposeSupported;
65             set => incrementalDisposeSupported = value;
66         }
67
68         public bool FullyDisposeNextCollect
69         {
70             get => fullCollectRequested;
71             set => fullCollectRequested = value;
72         }
73
74         public void Initialize()
75         {
76             if (!initialied)
77             {
78                 disposeQueueProcessDisposablesDelegate = new EventThreadCallback.CallbackDelegate(ProcessDisposables);
79                 eventThreadCallback = new EventThreadCallback(disposeQueueProcessDisposablesDelegate);
80                 initialied = true;
81
82                 DebugFileLogging.Instance.WriteLog("DiposeTest START");
83             }
84         }
85
86         public void Add(IDisposable disposable)
87         {
88             lock (listLock)
89             {
90                 disposables.Add(disposable);
91             }
92
93             if (initialied && eventThreadCallback != null && !eventThreadCallbackTriggered)
94             {
95                 eventThreadCallbackTriggered = true;
96                 eventThreadCallback.Trigger();
97             }
98         }
99
100         public void TriggerProcessDisposables(object o, EventArgs e)
101         {
102             processorRegistered = false;
103
104             if (eventThreadCallback != null && !eventThreadCallbackTriggered)
105             {
106                 eventThreadCallbackTriggered = true;
107                 eventThreadCallback.Trigger();
108             }
109         }
110
111         public void ProcessDisposables()
112         {
113             eventThreadCallbackTriggered = false;
114
115             lock (listLock)
116             {
117                 if (disposables.Count > 0)
118                 {
119                     DebugFileLogging.Instance.WriteLog($"Newly add {disposables.Count} count of disposables. Total disposables count is {incrementallyDisposedQueue.Count + disposables.Count}.\n");
120                     // Move item from end, due to the performance issue.
121                     while (disposables.Count > 0)
122                     {
123                         var disposable = disposables.Last();
124                         disposables.RemoveAt(disposables.Count - 1);
125                         incrementallyDisposedQueue.Add(disposable);
126                     }
127                     disposables.Clear();
128                 }
129             }
130
131             if (incrementallyDisposedQueue.Count > 0)
132             {
133                 if (!incrementalDisposeSupported ||
134                     (!fullCollectRequested && !ProcessorController.Instance.Initialized))
135                 {
136                     // Full Dispose if IncrementalDisposeSupported is false, or ProcessorController is not initialized yet.
137                     fullCollectRequested = true;
138                 }
139                 ProcessDisposablesIncrementally();
140             }
141         }
142
143         private void ProcessDisposablesIncrementally()
144         {
145             var disposeCount = fullCollectRequested ? incrementallyDisposedQueue.Count
146                                                     : Math.Min(incrementallyDisposedQueue.Count, Math.Max(minimumIncrementalCount, incrementallyDisposedQueue.Count * minimumIncrementalRate / 100));
147
148             DebugFileLogging.Instance.WriteLog((fullCollectRequested ? "Fully" : "Incrementally") + $" dispose {disposeCount} disposables. Will remained disposables count is {incrementallyDisposedQueue.Count - disposeCount}.\n");
149
150             fullCollectRequested = false;
151
152             // Dispose item from end, due to the performance issue.
153             while (disposeCount > 0 && incrementallyDisposedQueue.Count > 0)
154             {
155                 --disposeCount;
156                 var disposable = incrementallyDisposedQueue.Last();
157                 incrementallyDisposedQueue.RemoveAt(incrementallyDisposedQueue.Count - 1);
158
159                 DebugFileLogging.Instance.WriteLog($"disposable.Dispose(); type={disposable.GetType().FullName}, hash={disposable.GetHashCode()}");
160                 disposable.Dispose();
161             }
162
163             if (incrementallyDisposedQueue.Count > 0)
164             {
165                 if (ProcessorController.Instance.Initialized && !processorRegistered)
166                 {
167                     processorRegistered = true;
168                     ProcessorController.Instance.ProcessorOnceEvent += TriggerProcessDisposables;
169                     ProcessorController.Instance.Awake();
170                 }
171             }
172
173             DebugFileLogging.Instance.WriteLog($"Incrementally dispose finished.\n");
174         }
175     }
176 }