1/*
2 * Copyright (c) 2021 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 "thread_ex.h"
17#include <sys/resource.h>
18#include <sys/prctl.h>
19#include <iostream>
20#include "utils_log.h"
21
22namespace OHOS {
23using ThreadFunc = int (*)(void*);
24using PThreadRoutine = void* (*) (void*);
25
26struct ThreadParam {
27    ThreadFunc startRoutine;
28    void* args;
29    int priority;
30    std::string name;
31
32    // prctl only support set the name of the calling process.
33    static int Proxy(const ThreadParam* t)
34    {
35        if (t == nullptr) {
36            UTILS_LOGD("invalid param.");
37            return -1;
38        }
39        ThreadFunc f = t->startRoutine;
40        void* userData = t->args;
41        int prio = t->priority;
42        std::string threadName = t->name;
43
44        delete t;
45
46        // set thread priority
47        (void)setpriority(PRIO_PROCESS, 0, prio);
48
49        // set thread name
50        if (!threadName.empty()) {
51            prctl(PR_SET_NAME, threadName.substr(0, MAX_THREAD_NAME_LEN).c_str(), 0, 0, 0);
52        }
53
54        return f(userData);
55    }
56};
57
58bool CreatePThread(ThreadParam& para, size_t stackSize, pthread_t *threadId)
59
60{
61    pthread_attr_t attr;
62    pthread_attr_init(&attr);
63
64    // create thread as "detached", so it cleans up after itself.
65    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
66
67    auto t = new ThreadParam; // t would be delete in ThreadParam::Proxy
68    t->startRoutine = para.startRoutine;
69    t->args = para.args;
70    t->priority = para.priority;
71    t->name = para.name;
72
73    para.startRoutine = reinterpret_cast<ThreadFunc>(&ThreadParam::Proxy);
74    para.args = t;
75
76    if (stackSize) {
77        pthread_attr_setstacksize(&attr, stackSize);
78    }
79
80    errno = 0;
81    pthread_t thread;
82    int result = pthread_create(&thread, &attr, reinterpret_cast<PThreadRoutine>(para.startRoutine), para.args);
83    pthread_attr_destroy(&attr);
84    para.args = nullptr;
85
86    if (result != 0) {
87        return false;
88    }
89
90    if (threadId != nullptr) {
91        *threadId = thread;
92    }
93
94    return true;
95}
96
97Thread::Thread()
98    : thread_(INVALID_PTHREAD_T), status_(ThreadStatus::OK), exitPending_(false), running_(false)
99{
100}
101
102Thread::~Thread()
103{
104}
105
106ThreadStatus Thread::Start(const std::string& name, int32_t priority, size_t stack)
107{
108    std::lock_guard<std::mutex> lk(lock_);
109    if (running_) {
110        // already started
111        return ThreadStatus::INVALID_OPERATION;
112    }
113
114    status_ = ThreadStatus::OK;
115    exitPending_ = false;
116    thread_ = INVALID_PTHREAD_T;
117    running_ = true;
118
119    ThreadParam para;
120    para.startRoutine = ThreadStart;
121    para.args = this;
122    para.name = name;
123    para.priority = priority;
124
125    bool res = CreatePThread(para, stack, &thread_);
126    if (!res) {
127        status_ = ThreadStatus::UNKNOWN_ERROR;   // something happened!
128        running_ = false;
129        thread_ = INVALID_PTHREAD_T;
130        return ThreadStatus::UNKNOWN_ERROR;
131    }
132
133    return ThreadStatus::OK;
134}
135
136ThreadStatus Thread::NotifyExitSync()
137{
138    // If the two thread IDs are equal, pthread_equal() returns a non-zero value; otherwise, it returns 0.
139    if (pthread_equal(thread_, pthread_self()) != 0) {
140        // don't call NotifyExitSync() from this !;
141        return ThreadStatus::WOULD_BLOCK;
142    }
143
144    std::unique_lock<std::mutex> lk(lock_);
145    exitPending_ = true;
146
147    while (running_) {
148        cvThreadExited_.wait(lk);
149    }
150
151    exitPending_ = false;
152
153    return status_;
154}
155
156void Thread::NotifyExitAsync()
157{
158    std::lock_guard<std::mutex> lk(lock_);
159    exitPending_ = true;
160}
161
162bool Thread::ReadyToWork()
163{
164    return true;
165}
166
167bool Thread::IsExitPending() const
168{
169    std::lock_guard<std::mutex> lk(lock_);
170    return exitPending_;
171}
172
173bool Thread::IsRunning() const
174{
175    std::lock_guard<std::mutex> lk(lock_);
176    return running_;
177}
178
179int Thread::ThreadStart(void* args)
180{
181    Thread* const self = static_cast<Thread*>(args);
182    bool first = true;
183
184    do {
185        bool result = false;
186        if (first) {
187            first = false;
188            if (self->ReadyToWork() && !self->IsExitPending()) {
189                result = self->Run();
190            }
191        } else {
192            result = self->Run();
193        }
194
195        {
196            std::unique_lock<std::mutex> lk(self->lock_);
197            if ((!result) || self->exitPending_) {
198                self->exitPending_ = true;
199                self->running_ = false;
200                self->thread_ = INVALID_PTHREAD_T;
201                self->cvThreadExited_.notify_all();
202                break;
203            }
204        }
205    } while (true);
206
207    return 0;
208}
209
210} // namespace OHOS
211