1 /*
2 * Copyright (c) 2023-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "libpandabase/taskmanager/task_scheduler.h"
17 #include "runtime/include/thread.h"
18 #include "runtime/mem/gc/gc.h"
19 #include "runtime/mem/gc/workers/gc_worker.h"
20
21 namespace ark::mem {
GCWorker(GC * gc)22 GCWorker::GCWorker(GC *gc) : gc_(gc)
23 {
24 auto internalAllocator = gc_->GetInternalAllocator();
25 gcTaskQueue_ = internalAllocator->New<GCQueueWithTime>(gc_);
26 ASSERT(gcTaskQueue_ != nullptr);
27 auto *vm = gc_->GetPandaVm();
28 ASSERT(vm != nullptr);
29 gcThread_ = internalAllocator->New<Thread>(vm, Thread::ThreadType::THREAD_TYPE_GC);
30 ASSERT(gcThread_ != nullptr);
31 if (gc_->GetSettings()->UseTaskManagerForGC()) {
32 gcRunner_ = [this]() { this->GCTaskRunner(); };
33 }
34 }
35
~GCWorker()36 GCWorker::~GCWorker()
37 {
38 auto internalAllocator = gc_->GetInternalAllocator();
39 internalAllocator->Delete(gcThread_);
40 internalAllocator->Delete(gcTaskQueue_);
41 }
42
43 /* static */
GCThreadLoop(GCWorker * gcWorker)44 void GCWorker::GCThreadLoop(GCWorker *gcWorker)
45 {
46 // We need to set VM to current_thread, since GC can call ObjectAccessor::GetBarrierSet() methods
47 ScopedCurrentThread gcCurrentThreadScope(gcWorker->gcThread_);
48
49 while (true) {
50 // Get gc task from local gc tasks queue
51 auto task = gcWorker->GetTask();
52 if (!gcWorker->gc_->IsGCRunning()) {
53 LOG(DEBUG, GC) << "Stopping GC thread";
54 break;
55 }
56 gcWorker->RunGC(std::move(task));
57 }
58 }
59
CreateAndStartWorker()60 void GCWorker::CreateAndStartWorker()
61 {
62 // If GC runs in place or Task manager is used for GC, so no need create separate internal GC worker
63 if (gc_->GetSettings()->RunGCInPlace()) {
64 return;
65 }
66 if (gc_->GetSettings()->UseTaskManagerForGC()) {
67 needToFinish_ = false;
68 return;
69 }
70 ASSERT(gc_->GetSettings()->UseThreadPoolForGC());
71 ASSERT(gcInternalThread_ == nullptr);
72 auto allocator = gc_->GetInternalAllocator();
73 gcInternalThread_ = allocator->New<std::thread>(GCWorker::GCThreadLoop, this);
74 ASSERT(gcInternalThread_ != nullptr);
75 auto setGcThreadNameResult = os::thread::SetThreadName(gcInternalThread_->native_handle(), "GCThread");
76 LOG_IF(setGcThreadNameResult != 0, ERROR, RUNTIME) << "Failed to set a name for the gc thread";
77 }
78
FinalizeAndDestroyWorker()79 void GCWorker::FinalizeAndDestroyWorker()
80 {
81 // Signal that no need to delay task running
82 gcTaskQueue_->Signal();
83 if (gc_->GetSettings()->UseTaskManagerForGC()) {
84 needToFinish_ = true;
85 gc_->GetWorkersTaskQueue()->WaitBackgroundTasks();
86 return;
87 }
88 ASSERT(gc_->GetSettings()->UseThreadPoolForGC());
89 // Internal GC thread was not created, so just return
90 if (gcInternalThread_ == nullptr) {
91 return;
92 }
93 gcInternalThread_->join();
94 gc_->GetInternalAllocator()->Delete(gcInternalThread_);
95 gcInternalThread_ = nullptr;
96 }
97
CreateAndAddTaskToTaskManager()98 void GCWorker::CreateAndAddTaskToTaskManager()
99 {
100 ASSERT_PRINT(gcRunner_ != nullptr, "Need to create task only for TaskManager case");
101 gc_->GetWorkersTaskQueue()->AddBackgroundTask(gcRunner_);
102 }
103
GCTaskRunner()104 void GCWorker::GCTaskRunner()
105 {
106 // only one task can get gc task from queue and run it
107 if (!gcTaskRunMutex_.TryLock()) {
108 // If any task is executed in TaskManager then current task should do nothing and just return
109 // According task for TaskManager will be created after RunGC if needed
110 return;
111 }
112 // Task manager does not know anything about panda threads, so set gc thread as current thread during task running
113 ScopedCurrentThread gcCurrentThreadScope(gcThread_);
114 auto gcTask = GetTask();
115 // If GC was not started then task should not be run, so delay the task execution
116 if (!gc_->IsGCRunning()) {
117 if (!needToFinish_) {
118 // Added task can run on another worker and try to lock gc_task_run_mutex_, but in the current worker we
119 // already held the mutex, so TryLock fails and task running cancels
120 // So unlock the mutex before adding task
121 gcTaskRunMutex_.Unlock();
122 AddTask(std::move(gcTask));
123 } else {
124 gcTaskRunMutex_.Unlock();
125 }
126 return;
127 }
128 RunGC(std::move(gcTask));
129 gcTaskRunMutex_.Unlock();
130 // If gc tasks queue has a task, so need to create Task for TaskManager to process it
131 if (!gcTaskQueue_->IsEmpty()) {
132 CreateAndAddTaskToTaskManager();
133 }
134 }
135
AddTask(PandaUniquePtr<GCTask> task)136 bool GCWorker::AddTask(PandaUniquePtr<GCTask> task)
137 {
138 bool wasAdded = gcTaskQueue_->AddTask(std::move(task));
139 // If Task Manager is used then create a new task for task manager and put it
140 if (wasAdded && gc_->GetSettings()->UseTaskManagerForGC()) {
141 CreateAndAddTaskToTaskManager();
142 }
143 return wasAdded;
144 }
145
GetTask()146 PandaUniquePtr<GCTask> GCWorker::GetTask()
147 {
148 auto fullGcBombingFreq = gc_->GetSettings()->FullGCBombingFrequency();
149 // 0 means full gc bombing is not used, so just return task from local queue
150 if (fullGcBombingFreq == 0U) {
151 return gcTaskQueue_->GetTask(gc_->GetSettings()->UseThreadPoolForGC());
152 }
153 // Need to bombs full GC in according with full gc bombing frequency
154 if (collectNumberMod_ == fullGcBombingFreq) {
155 collectNumberMod_ = 1;
156 return MakePandaUnique<GCTask>(GCTaskCause::OOM_CAUSE, time::GetCurrentTimeInNanos());
157 }
158 ++collectNumberMod_;
159 return gcTaskQueue_->GetTask(gc_->GetSettings()->UseThreadPoolForGC());
160 }
161
RunGC(PandaUniquePtr<GCTask> task)162 void GCWorker::RunGC(PandaUniquePtr<GCTask> task)
163 {
164 if (task == nullptr || task->reason == GCTaskCause::INVALID_CAUSE) {
165 return;
166 }
167 if (gc_->IsPostponeEnabled() && (task->reason == GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE)) {
168 os::memory::LockHolder lh(postponedTasksMutex_);
169 if (postponedTasks_.empty() || postponedTasks_.back()->reason != task->reason) {
170 postponedTasks_.push(std::move(task));
171 }
172 return;
173 }
174 LOG(DEBUG, GC) << "Running GC task, reason " << task->reason;
175 task->Run(*gc_);
176 }
177
OnPostponeGCEnd()178 void GCWorker::OnPostponeGCEnd()
179 {
180 os::memory::LockHolder lh(postponedTasksMutex_);
181 while (!postponedTasks_.empty()) {
182 AddTask(std::move(postponedTasks_.back()));
183 postponedTasks_.pop();
184 }
185 }
186
187 } // namespace ark::mem
188