1 /*
2 * Copyright (c) 2023-2024 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 taskmanager::TaskScheduler::GetTaskScheduler()->WaitForFinishAllTasksWithProperties(GC_WORKER_TASK_PROPERTIES);
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 auto gcTaskmanagerTask = taskmanager::Task::Create(GC_WORKER_TASK_PROPERTIES, gcRunner_);
102 gc_->GetWorkersTaskQueue()->AddTask(std::move(gcTaskmanagerTask));
103 }
104
GCTaskRunner()105 void GCWorker::GCTaskRunner()
106 {
107 // only one task can get gc task from queue and run it
108 if (!gcTaskRunMutex_.TryLock()) {
109 // If any task is executed in TaskManager then current task should do nothing and just return
110 // According task for TaskManager will be created after RunGC if needed
111 return;
112 }
113 // Task manager does not know anything about panda threads, so set gc thread as current thread during task running
114 ScopedCurrentThread gcCurrentThreadScope(gcThread_);
115 auto gcTask = GetTask();
116 // If GC was not started then task should not be run, so delay the task execution
117 if (!gc_->IsGCRunning()) {
118 if (!needToFinish_) {
119 // Added task can run on another worker and try to lock gc_task_run_mutex_, but in the current worker we
120 // already held the mutex, so TryLock fails and task running cancels
121 // So unlock the mutex before adding task
122 gcTaskRunMutex_.Unlock();
123 AddTask(std::move(gcTask));
124 } else {
125 gcTaskRunMutex_.Unlock();
126 }
127 return;
128 }
129 RunGC(std::move(gcTask));
130 gcTaskRunMutex_.Unlock();
131 // If gc tasks queue has a task, so need to create Task for TaskManager to process it
132 if (!gcTaskQueue_->IsEmpty()) {
133 CreateAndAddTaskToTaskManager();
134 }
135 }
136
AddTask(PandaUniquePtr<GCTask> task)137 bool GCWorker::AddTask(PandaUniquePtr<GCTask> task)
138 {
139 bool wasAdded = gcTaskQueue_->AddTask(std::move(task));
140 // If Task Manager is used then create a new task for task manager and put it
141 if (wasAdded && gc_->GetSettings()->UseTaskManagerForGC()) {
142 CreateAndAddTaskToTaskManager();
143 }
144 return wasAdded;
145 }
146
GetTask()147 PandaUniquePtr<GCTask> GCWorker::GetTask()
148 {
149 auto fullGcBombingFreq = gc_->GetSettings()->FullGCBombingFrequency();
150 // 0 means full gc bombing is not used, so just return task from local queue
151 if (fullGcBombingFreq == 0U) {
152 return gcTaskQueue_->GetTask(gc_->GetSettings()->UseThreadPoolForGC());
153 }
154 // Need to bombs full GC in according with full gc bombing frequency
155 if (collectNumberMod_ == fullGcBombingFreq) {
156 collectNumberMod_ = 1;
157 return MakePandaUnique<GCTask>(GCTaskCause::OOM_CAUSE, time::GetCurrentTimeInNanos());
158 }
159 ++collectNumberMod_;
160 return gcTaskQueue_->GetTask(gc_->GetSettings()->UseThreadPoolForGC());
161 }
162
RunGC(PandaUniquePtr<GCTask> task)163 void GCWorker::RunGC(PandaUniquePtr<GCTask> task)
164 {
165 if (task == nullptr || task->reason == GCTaskCause::INVALID_CAUSE) {
166 return;
167 }
168 if (gc_->IsPostponeEnabled() && task->reason == GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE) {
169 // If GC was postponed then return task back to local gc tasks queue
170 this->AddTask(std::move(task));
171 // In task manager case worker does not wait new task, otherwise we capture this worker and decrese
172 // possibilities for Task manager usages
173 if (gc_->GetSettings()->UseThreadPoolForGC()) {
174 gcTaskQueue_->WaitForGCTask();
175 }
176 return;
177 }
178 LOG(DEBUG, GC) << "Running GC task, reason " << task->reason;
179 task->Run(*gc_);
180 }
181
182 } // namespace ark::mem
183