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