[NUI] Revert dispose synchronously logic when APP is not started
[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 initialized = 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             initialized = false;
55             if (processorRegistered && ProcessorController.Instance.Initialized)
56             {
57                 processorRegistered = false;
58                 ProcessorController.Instance.ProcessorOnceEvent -= TriggerProcessDisposables;
59             }
60         }
61
62         public static DisposeQueue Instance
63         {
64             get { return disposableQueue; }
65         }
66
67         public bool IncrementalDisposeSupported
68         {
69             get => incrementalDisposeSupported;
70             set => incrementalDisposeSupported = value;
71         }
72
73         public bool FullyDisposeNextCollect
74         {
75             get => fullCollectRequested;
76             set => fullCollectRequested = value;
77         }
78
79         public void Initialize()
80         {
81             if (!initialized)
82             {
83                 disposeQueueProcessDisposablesDelegate = new EventThreadCallback.CallbackDelegate(ProcessDisposables);
84                 eventThreadCallback = new EventThreadCallback(disposeQueueProcessDisposablesDelegate);
85                 initialized = true;
86
87                 DebugFileLogging.Instance.WriteLog("DiposeTest START");
88             }
89         }
90
91         public void Add(IDisposable disposable)
92         {
93             lock (listLock)
94             {
95                 disposables.Add(disposable);
96             }
97
98             if (initialized && eventThreadCallback != null)
99             {
100                 if (!eventThreadCallbackTriggered)
101                 {
102                     eventThreadCallbackTriggered = true;
103                     eventThreadCallback.Trigger();
104                 }
105             }
106             else
107             {
108                 // Flush Disposable queue synchronously if it is not initialized yet.
109                 // TODO : Need to check thread here if we need.
110
111                 // 2023-12-18 Block this logic since some APP call some thread-dependency objects before application start.
112                 // ProcessDisposables();
113             }
114         }
115
116         public void TriggerProcessDisposables(object o, EventArgs e)
117         {
118             processorRegistered = false;
119
120             if (initialized && eventThreadCallback != null)
121             {
122                 if (!eventThreadCallbackTriggered)
123                 {
124                     eventThreadCallbackTriggered = true;
125                     eventThreadCallback.Trigger();
126                 }
127             }
128             else
129             {
130                 // Flush Disposable queue synchronously if it is not initialized yet.
131                 // TODO : Need to check thread here if we need.
132
133                 // 2023-12-18 Block this logic since some APP call some thread-dependency objects before application start.
134                 // ProcessDisposables();
135             }
136         }
137
138         public void ProcessDisposables()
139         {
140             eventThreadCallbackTriggered = false;
141
142             lock (listLock)
143             {
144                 if (disposables.Count > 0)
145                 {
146                     DebugFileLogging.Instance.WriteLog($"Newly add {disposables.Count} count of disposables. Total disposables count is {incrementallyDisposedQueue.Count + disposables.Count}.\n");
147                     // Move item from end, due to the performance issue.
148                     while (disposables.Count > 0)
149                     {
150                         var disposable = disposables.Last();
151                         disposables.RemoveAt(disposables.Count - 1);
152                         incrementallyDisposedQueue.Add(disposable);
153                     }
154                     disposables.Clear();
155                 }
156             }
157
158             if (incrementallyDisposedQueue.Count > 0)
159             {
160                 if (!incrementalDisposeSupported ||
161                     (!fullCollectRequested && !ProcessorController.Instance.Initialized))
162                 {
163                     // Full Dispose if IncrementalDisposeSupported is false, or ProcessorController is not initialized yet.
164                     fullCollectRequested = true;
165                 }
166                 ProcessDisposablesIncrementally();
167             }
168         }
169
170         private void ProcessDisposablesIncrementally()
171         {
172             var disposeCount = fullCollectRequested ? incrementallyDisposedQueue.Count
173                                                     : Math.Min(incrementallyDisposedQueue.Count, Math.Max(minimumIncrementalCount, incrementallyDisposedQueue.Count * minimumIncrementalRate / 100));
174
175             DebugFileLogging.Instance.WriteLog((fullCollectRequested ? "Fully" : "Incrementally") + $" dispose {disposeCount} disposables. Will remained disposables count is {incrementallyDisposedQueue.Count - disposeCount}.\n");
176
177             fullCollectRequested = false;
178
179             // Dispose item from end, due to the performance issue.
180             while (disposeCount > 0 && incrementallyDisposedQueue.Count > 0)
181             {
182                 --disposeCount;
183                 var disposable = incrementallyDisposedQueue.Last();
184                 incrementallyDisposedQueue.RemoveAt(incrementallyDisposedQueue.Count - 1);
185
186                 DebugFileLogging.Instance.WriteLog($"disposable.Dispose(); type={disposable.GetType().FullName}, hash={disposable.GetHashCode()}");
187                 disposable.Dispose();
188             }
189
190             if (incrementallyDisposedQueue.Count > 0)
191             {
192                 if (ProcessorController.Instance.Initialized && !processorRegistered)
193                 {
194                     processorRegistered = true;
195                     ProcessorController.Instance.ProcessorOnceEvent += TriggerProcessDisposables;
196                     ProcessorController.Instance.Awake();
197                 }
198             }
199
200             DebugFileLogging.Instance.WriteLog($"Incrementally dispose finished.\n");
201         }
202     }
203 }