• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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