| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title Os/Posix/Task.cpp | ||
| 3 | // \brief implementation of Posix implementation of Os::Task | ||
| 4 | // ====================================================================== | ||
| 5 | #include <pthread.h> | ||
| 6 | #include <unistd.h> | ||
| 7 | #include <cerrno> | ||
| 8 | #include <climits> | ||
| 9 | #include <cstring> | ||
| 10 | |||
| 11 | #include "Fw/Logger/Logger.hpp" | ||
| 12 | #include "Fw/Types/Assert.hpp" | ||
| 13 | #include "Fw/Types/StringUtils.hpp" | ||
| 14 | #include "Os/Posix/Task.hpp" | ||
| 15 | #include "Os/Posix/error.hpp" | ||
| 16 | #include "Os/Task.hpp" | ||
| 17 | |||
| 18 | namespace Os { | ||
| 19 | namespace Posix { | ||
| 20 | namespace Task { | ||
| 21 | std::atomic<bool> PosixTask::s_permissions_reported(false); | ||
| 22 | static const int SCHED_POLICY = SCHED_RR; | ||
| 23 | static const int SCHED_POLICY_NON_REALTIME = SCHED_OTHER; | ||
| 24 | |||
| 25 | typedef void* (*pthread_func_ptr)(void*); | ||
| 26 | |||
| 27 | // Forward declaration | ||
| 28 | int set_task_name(pthread_t thread, char* name); | ||
| 29 | |||
| 30 | 446 | void* pthread_entry_wrapper(void* wrapper_pointer) { | |
| 31 | 446 | FW_ASSERT(wrapper_pointer != nullptr); | |
| 32 | // Both downcasts are safe because we know the types | ||
| 33 | 446 | Os::Task::TaskRoutineWrapper& wrapper = *reinterpret_cast<Os::Task::TaskRoutineWrapper*>(wrapper_pointer); | |
| 34 | #if defined(POSIX_THREADS_ENABLE_NAMES) && POSIX_THREADS_ENABLE_NAMES | ||
| 35 | 446 | auto handle = reinterpret_cast<Os::Posix::Task::PosixTaskHandle*>(wrapper.m_task.getHandle()); | |
| 36 | 446 | FW_ASSERT(handle != nullptr); | |
| 37 | // Task name is on a best effort basis. Use pthread_self() since the handle's task | ||
| 38 | // descriptor is written by pthread_create concurrently with this thread's start. | ||
| 39 | 446 | (void)set_task_name(pthread_self(), handle->m_name); | |
| 40 | #endif | ||
| 41 | 446 | wrapper.run(&wrapper); | |
| 42 | 446 | return nullptr; | |
| 43 | } | ||
| 44 | |||
| 45 | ✗ | int set_stack_size(pthread_attr_t& attributes, const Os::Task::Arguments& arguments) { | |
| 46 | ✗ | int status = PosixTaskHandle::SUCCESS; | |
| 47 | ✗ | FwSizeType stack = arguments.m_stackSize; | |
| 48 | // Check for stack size multiple of page size or skip when the function | ||
| 49 | // is unavailable. | ||
| 50 | #ifdef _SC_PAGESIZE | ||
| 51 | ✗ | long page_size = sysconf(_SC_PAGESIZE); | |
| 52 | #else | ||
| 53 | long page_size = -1; // Force skip and warning | ||
| 54 | #endif | ||
| 55 | ✗ | if (page_size <= 0) { | |
| 56 | ✗ | Fw::Logger::log("[WARNING] %s could not determine page size %s. Skipping stack-size check.\n", | |
| 57 | ✗ | const_cast<CHAR*>(arguments.m_name.toChar()), strerror(errno)); | |
| 58 | ✗ | } else if ((stack % static_cast<FwSizeType>(page_size)) != 0) { | |
| 59 | // Round-down to nearest page size multiple | ||
| 60 | ✗ | FwSizeType rounded = (stack / static_cast<FwSizeType>(page_size)) * static_cast<FwSizeType>(page_size); | |
| 61 | ✗ | Fw::Logger::log("[WARNING] %s stack size of %" PRI_FwSizeType | |
| 62 | " is not multiple of page size %ld, rounding to %" PRI_FwSizeType "\n", | ||
| 63 | ✗ | const_cast<CHAR*>(arguments.m_name.toChar()), stack, page_size, rounded); | |
| 64 | ✗ | stack = rounded; | |
| 65 | } | ||
| 66 | |||
| 67 | // Clamp invalid stack sizes | ||
| 68 | ✗ | if (stack <= static_cast<FwSizeType>(PTHREAD_STACK_MIN)) { | |
| 69 | ✗ | Fw::Logger::log( | |
| 70 | "[WARNING] %s stack size of %" PRI_FwSizeType " is too small, clamping to %" PRI_FwSizeType "\n", | ||
| 71 | ✗ | const_cast<CHAR*>(arguments.m_name.toChar()), stack, static_cast<FwSizeType>(PTHREAD_STACK_MIN)); | |
| 72 | ✗ | stack = static_cast<FwSizeType>(PTHREAD_STACK_MIN); | |
| 73 | } | ||
| 74 | // Clamping above guarantees a valid minimum stack size | ||
| 75 | ✗ | FW_ASSERT(stack >= static_cast<FwSizeType>(PTHREAD_STACK_MIN), static_cast<FwAssertArgType>(stack)); | |
| 76 | ✗ | status = pthread_attr_setstacksize(&attributes, static_cast<size_t>(stack)); | |
| 77 | ✗ | return status; | |
| 78 | } | ||
| 79 | |||
| 80 | 1 | int set_priority_params(pthread_attr_t& attributes, const Os::Task::Arguments& arguments) { | |
| 81 | 1 | const FwSizeType min_priority = static_cast<FwSizeType>(sched_get_priority_min(SCHED_POLICY)); | |
| 82 | 1 | const FwSizeType max_priority = static_cast<FwSizeType>(sched_get_priority_max(SCHED_POLICY)); | |
| 83 | 1 | int status = PosixTaskHandle::SUCCESS; | |
| 84 | 1 | FwSizeType priority = arguments.m_priority; | |
| 85 | // Clamp to minimum priority | ||
| 86 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (priority < min_priority) { |
| 87 | ✗ | Fw::Logger::log("[WARNING] %s low task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n", | |
| 88 | ✗ | const_cast<CHAR*>(arguments.m_name.toChar()), priority, min_priority); | |
| 89 | ✗ | priority = min_priority; | |
| 90 | } | ||
| 91 | // Clamp to maximum priority | ||
| 92 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | else if (priority > max_priority) { |
| 93 | ✗ | Fw::Logger::log("[WARNING] %s high task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n", | |
| 94 | ✗ | const_cast<CHAR*>(arguments.m_name.toChar()), priority, max_priority); | |
| 95 | ✗ | priority = max_priority; | |
| 96 | } | ||
| 97 | |||
| 98 | // Clamping above guarantees the priority is within the policy's valid range | ||
| 99 | 1 | FW_ASSERT(priority >= min_priority && priority <= max_priority, static_cast<FwAssertArgType>(priority)); | |
| 100 | |||
| 101 | // Set attributes required for priority | ||
| 102 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | status = pthread_attr_setschedpolicy(&attributes, SCHED_POLICY); |
| 103 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (status == PosixTaskHandle::SUCCESS) { |
| 104 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | status = pthread_attr_setinheritsched(&attributes, PTHREAD_EXPLICIT_SCHED); |
| 105 | } | ||
| 106 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (status == PosixTaskHandle::SUCCESS) { |
| 107 | 1 | sched_param schedParam; | |
| 108 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | (void)memset(&schedParam, 0, sizeof(sched_param)); |
| 109 | 1 | schedParam.sched_priority = static_cast<int>(priority); | |
| 110 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
1 | status = pthread_attr_setschedparam(&attributes, &schedParam); |
| 111 | } | ||
| 112 | 1 | return status; | |
| 113 | } | ||
| 114 | |||
| 115 | 1 | int set_non_realtime_params(pthread_attr_t& attributes) { | |
| 116 | // Lowest priority of the non-realtime policy (0 on Linux). Set explicitly: otherwise the creating thread's | ||
| 117 | // (possibly realtime) priority is inherited and rejected by pthread_create when combined with SCHED_OTHER. | ||
| 118 | 1 | const int priority = sched_get_priority_min(SCHED_POLICY_NON_REALTIME); | |
| 119 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | int status = (priority >= 0) ? PosixTaskHandle::SUCCESS : errno; |
| 120 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (status == PosixTaskHandle::SUCCESS) { |
| 121 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | status = pthread_attr_setschedpolicy(&attributes, SCHED_POLICY_NON_REALTIME); |
| 122 | } | ||
| 123 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (status == PosixTaskHandle::SUCCESS) { |
| 124 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | status = pthread_attr_setinheritsched(&attributes, PTHREAD_EXPLICIT_SCHED); |
| 125 | } | ||
| 126 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (status == PosixTaskHandle::SUCCESS) { |
| 127 | 1 | sched_param schedParam; | |
| 128 | 1 | (void)memset(&schedParam, 0, sizeof(sched_param)); | |
| 129 | 1 | schedParam.sched_priority = priority; | |
| 130 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | status = pthread_attr_setschedparam(&attributes, &schedParam); |
| 131 | } | ||
| 132 | 1 | return status; | |
| 133 | } | ||
| 134 | |||
| 135 | ✗ | int set_cpu_affinity(pthread_attr_t& attributes, const Os::Task::Arguments& arguments) { | |
| 136 | ✗ | int status = 0; | |
| 137 | // pthread_attr_setaffinity_np is a non-POSIX function. Notably, it is not available on musl. | ||
| 138 | // Limit its use to builds that involve glibc, on Linux, with _GNU_SOURCE defined. | ||
| 139 | // That's the circumstance in which we expect this feature to work. | ||
| 140 | #if defined(TGT_OS_TYPE_LINUX) && defined(__GLIBC__) && defined(_GNU_SOURCE) | ||
| 141 | ✗ | const FwSizeType affinity = arguments.m_cpuAffinity; | |
| 142 | // CPU_SET is undefined for indices at or beyond CPU_SETSIZE | ||
| 143 | ✗ | FW_ASSERT(affinity < static_cast<FwSizeType>(CPU_SETSIZE), static_cast<FwAssertArgType>(affinity)); | |
| 144 | ✗ | cpu_set_t cpu_set; | |
| 145 | ✗ | CPU_ZERO(&cpu_set); | |
| 146 | ✗ | CPU_SET(static_cast<int>(affinity), &cpu_set); | |
| 147 | |||
| 148 | // According to the man-page this function sets errno rather than returning an error status like other functions | ||
| 149 | ✗ | status = pthread_attr_setaffinity_np(&attributes, sizeof(cpu_set_t), &cpu_set); | |
| 150 | ✗ | status = (status == PosixTaskHandle::SUCCESS) ? status : errno; | |
| 151 | #else | ||
| 152 | Fw::Logger::log("[WARNING] %s setting CPU affinity is only available with GNU pthreads\n", | ||
| 153 | const_cast<CHAR*>(arguments.m_name.toChar())); | ||
| 154 | #endif | ||
| 155 | ✗ | return status; | |
| 156 | } | ||
| 157 | |||
| 158 | 446 | int set_task_name(pthread_t thread, char* name) { | |
| 159 | 446 | FW_ASSERT(name != nullptr); | |
| 160 | 446 | int status = 0; | |
| 161 | // pthread_setname_np is a non-POSIX function. | ||
| 162 | // Limit its use to builds that involve glibc, on Linux, with _GNU_SOURCE defined. | ||
| 163 | // That's the circumstance in which we expect this feature to work. | ||
| 164 | #if defined(TGT_OS_TYPE_LINUX) && defined(__GLIBC__) && defined(_GNU_SOURCE) && defined(POSIX_THREADS_ENABLE_NAMES) && \ | ||
| 165 | POSIX_THREADS_ENABLE_NAMES | ||
| 166 | // Force safe name usage | ||
| 167 | 446 | name[Os::Posix::Task::PosixTaskHandle::PTHREAD_NAME_LENGTH - 1] = '\0'; | |
| 168 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 446 times.
|
446 | status = pthread_setname_np(thread, name); |
| 169 | #endif | ||
| 170 | 446 | return status; | |
| 171 | } | ||
| 172 | |||
| 173 | 447 | Os::Task::Status PosixTask::create(const Os::Task::Arguments& arguments, | |
| 174 | const PosixTask::PermissionExpectation permissions) { | ||
| 175 | 447 | int pthread_status = PosixTaskHandle::SUCCESS; | |
| 176 | 447 | PosixTaskHandle& handle = this->m_handle; | |
| 177 | 447 | const bool expect_permission = (permissions == EXPECT_PERMISSION); | |
| 178 | // Initialize and clear pthread attributes | ||
| 179 | 447 | pthread_attr_t attributes; | |
| 180 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 447 times.
|
447 | (void)memset(&attributes, 0, sizeof(attributes)); |
| 181 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 447 times.
|
447 | pthread_status = pthread_attr_init(&attributes); |
| 182 | // Setting the stack size requires no special permission | ||
| 183 |
1/4✗ Branch 2 not taken.
✓ Branch 3 taken 447 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
447 | if ((arguments.m_stackSize != Os::Task::TASK_DEFAULT) && (pthread_status == PosixTaskHandle::SUCCESS)) { |
| 184 | ✗ | pthread_status = set_stack_size(attributes, arguments); | |
| 185 | } | ||
| 186 | // Non-realtime scheduling requires no special permission; realtime priorities do | ||
| 187 |
3/4✓ Branch 2 taken 1 times.
✓ Branch 3 taken 446 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
447 | if ((arguments.m_priority == PosixTask::TASK_PRIORITY_NON_REALTIME) && |
| 188 | (pthread_status == PosixTaskHandle::SUCCESS)) { | ||
| 189 | 1 | pthread_status = set_non_realtime_params(attributes); | |
| 190 |
5/6✓ Branch 2 taken 2 times.
✓ Branch 3 taken 444 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
|
446 | } else if ((arguments.m_priority != Os::Task::TASK_PRIORITY_DEFAULT) && (expect_permission) && |
| 191 | (pthread_status == PosixTaskHandle::SUCCESS)) { | ||
| 192 |
1/1✓ Branch 1 taken 1 times.
|
1 | pthread_status = set_priority_params(attributes, arguments); |
| 193 | } | ||
| 194 |
1/6✗ Branch 2 not taken.
✓ Branch 3 taken 447 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
447 | if ((arguments.m_cpuAffinity != Os::Task::TASK_DEFAULT) && (expect_permission) && |
| 195 | (pthread_status == PosixTaskHandle::SUCCESS)) { | ||
| 196 | ✗ | pthread_status = set_cpu_affinity(attributes, arguments); | |
| 197 | } | ||
| 198 | #if defined(POSIX_THREADS_ENABLE_NAMES) && POSIX_THREADS_ENABLE_NAMES | ||
| 199 | // Copy the name before the thread starts, since the new thread reads it | ||
| 200 |
1/1✓ Branch 10 taken 447 times.
|
447 | (void)Fw::StringUtils::string_copy(handle.m_name, arguments.m_name.toChar(), sizeof(handle.m_name)); |
| 201 | #endif | ||
| 202 | |||
| 203 |
1/2✓ Branch 0 taken 447 times.
✗ Branch 1 not taken.
|
447 | if (pthread_status == PosixTaskHandle::SUCCESS) { |
| 204 | pthread_status = | ||
| 205 |
2/4✗ Branch 3 not taken.
✓ Branch 4 taken 447 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 447 times.
|
447 | pthread_create(&handle.m_task_descriptor, &attributes, pthread_entry_wrapper, arguments.m_routine_argument); |
| 206 | } | ||
| 207 | // Successful execution of all precious steps will result in a valid task handle | ||
| 208 |
2/2✓ Branch 0 taken 446 times.
✓ Branch 1 taken 1 times.
|
447 | if (pthread_status == PosixTaskHandle::SUCCESS) { |
| 209 | 446 | handle.m_is_valid = true; | |
| 210 | } | ||
| 211 | |||
| 212 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 447 times.
|
447 | (void)pthread_attr_destroy(&attributes); |
| 213 |
1/1✓ Branch 1 taken 447 times.
|
894 | return Posix::posix_status_to_task_status(pthread_status); |
| 214 | } | ||
| 215 | |||
| 216 | 446 | void PosixTask::onStart() {} | |
| 217 | |||
| 218 | 446 | Os::Task::Status PosixTask::start(const Arguments& arguments) { | |
| 219 | 446 | FW_ASSERT(arguments.m_routine != nullptr); | |
| 220 | |||
| 221 | // Try to create thread with assuming permissions | ||
| 222 | 446 | Os::Task::Status status = this->create(arguments, PermissionExpectation::EXPECT_PERMISSION); | |
| 223 | // Failure due to permission automatically retried | ||
| 224 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 445 times.
|
446 | if (status == Os::Task::Status::ERROR_PERMISSION) { |
| 225 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | if (not PosixTask::s_permissions_reported) { |
| 226 | 1 | Fw::Logger::log("\n"); | |
| 227 | 1 | Fw::Logger::log("[NOTE] Task Permissions:\n"); | |
| 228 | 1 | Fw::Logger::log("[NOTE]\n"); | |
| 229 | 1 | Fw::Logger::log( | |
| 230 | "[NOTE] You have insufficient permissions to create a task with priority and/or cpu affinity.\n"); | ||
| 231 | 1 | Fw::Logger::log("[NOTE] A task without priority and affinity will be created.\n"); | |
| 232 | 1 | Fw::Logger::log("[NOTE]\n"); | |
| 233 | 1 | Fw::Logger::log("[NOTE] There are three possible resolutions:\n"); | |
| 234 | 1 | Fw::Logger::log("[NOTE] 1. Use tasks without priority and affinity using parameterless start()\n"); | |
| 235 | 1 | Fw::Logger::log("[NOTE] 2. Run this executable as a user with task priority permission\n"); | |
| 236 | 1 | Fw::Logger::log("[NOTE] 3. Grant capability with \"setcap 'cap_sys_nice=eip'\" or equivalent\n"); | |
| 237 | 1 | Fw::Logger::log("\n"); | |
| 238 | 1 | PosixTask::s_permissions_reported = true; | |
| 239 | } | ||
| 240 | // Fallback with no permission | ||
| 241 | 1 | status = this->create(arguments, PermissionExpectation::EXPECT_NO_PERMISSION); | |
| 242 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 445 times.
|
445 | } else if (status != Os::Task::Status::OP_OK) { |
| 243 | ✗ | Fw::Logger::log("[ERROR] Failed to create task with status: %d", static_cast<int>(status)); | |
| 244 | } | ||
| 245 | 446 | return status; | |
| 246 | } | ||
| 247 | |||
| 248 | 446 | Os::Task::Status PosixTask::join() { | |
| 249 | 446 | Os::Task::Status status = Os::Task::Status::JOIN_ERROR; | |
| 250 |
2/4✗ Branch 3 not taken.
✓ Branch 4 taken 446 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 446 times.
|
446 | if (not this->m_handle.m_is_valid) { |
| 251 | ✗ | status = Os::Task::Status::INVALID_HANDLE; | |
| 252 | } else { | ||
| 253 | 446 | int stat = ::pthread_join(this->m_handle.m_task_descriptor, nullptr); | |
| 254 |
1/2✓ Branch 0 taken 446 times.
✗ Branch 1 not taken.
|
446 | status = (stat == PosixTaskHandle::SUCCESS) ? Os::Task::Status::OP_OK : Os::Task::Status::JOIN_ERROR; |
| 255 | } | ||
| 256 | 446 | return status; | |
| 257 | } | ||
| 258 | |||
| 259 | 446 | TaskHandle* PosixTask::getHandle() { | |
| 260 | 446 | return &this->m_handle; | |
| 261 | } | ||
| 262 | |||
| 263 | // Note: not implemented for Posix threads. Must be manually done using a mutex or other blocking construct as there | ||
| 264 | // is no top-level pthreads support for suspend and resume. | ||
| 265 | ✗ | void PosixTask::suspend(Os::Task::SuspensionType suspensionType) { | |
| 266 | ✗ | FW_ASSERT(false); | |
| 267 | ✗ | } | |
| 268 | |||
| 269 | ✗ | void PosixTask::resume() { | |
| 270 | ✗ | FW_ASSERT(false); | |
| 271 | ✗ | } | |
| 272 | |||
| 273 | 9210 | Os::Task::Status PosixTask::_delay(const Fw::TimeInterval& interval) { | |
| 274 | 9210 | Os::Task::Status task_status = Os::Task::OP_OK; | |
| 275 | 9210 | timespec sleep_interval; | |
| 276 |
1/1✓ Branch 4 taken 9210 times.
|
9210 | sleep_interval.tv_sec = interval.getSeconds(); |
| 277 |
1/1✓ Branch 4 taken 9210 times.
|
9210 | sleep_interval.tv_nsec = interval.getUSeconds() * 1000; |
| 278 | |||
| 279 | 9210 | timespec remaining_interval; | |
| 280 | 9210 | remaining_interval.tv_sec = 0; | |
| 281 | 9210 | remaining_interval.tv_nsec = 0; | |
| 282 | |||
| 283 | while (true) { | ||
| 284 |
1/1✓ Branch 1 taken 9210 times.
|
9210 | int status = nanosleep(&sleep_interval, &remaining_interval); |
| 285 | // Success, return ok | ||
| 286 |
1/2✓ Branch 0 taken 9210 times.
✗ Branch 1 not taken.
|
9210 | if (0 == status) { |
| 287 | 9210 | break; | |
| 288 | } | ||
| 289 | // Interrupted, reset sleep and iterate | ||
| 290 | ✗ | else if (EINTR == errno) { | |
| 291 | ✗ | sleep_interval = remaining_interval; | |
| 292 | ✗ | continue; | |
| 293 | } | ||
| 294 | // Anything else is an error | ||
| 295 | else { | ||
| 296 | ✗ | task_status = Os::Task::Status::DELAY_ERROR; | |
| 297 | ✗ | break; | |
| 298 | } | ||
| 299 | ✗ | } | |
| 300 | 18420 | return task_status; | |
| 301 | } | ||
| 302 | |||
| 303 | } // end namespace Task | ||
| 304 | } // end namespace Posix | ||
| 305 | } // end namespace Os | ||
| 306 |