diff options
| -rw-r--r-- | src/core/hle/kernel/process.cpp | 87 | ||||
| -rw-r--r-- | src/core/hle/kernel/process.h | 55 | ||||
| -rw-r--r-- | src/core/hle/kernel/svc.cpp | 32 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.cpp | 58 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.h | 13 |
5 files changed, 145 insertions, 100 deletions
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 914bbe0a1..121f741fd 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp | |||
| @@ -7,10 +7,12 @@ | |||
| 7 | #include "common/assert.h" | 7 | #include "common/assert.h" |
| 8 | #include "common/common_funcs.h" | 8 | #include "common/common_funcs.h" |
| 9 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 10 | #include "core/core.h" | ||
| 10 | #include "core/hle/kernel/errors.h" | 11 | #include "core/hle/kernel/errors.h" |
| 11 | #include "core/hle/kernel/kernel.h" | 12 | #include "core/hle/kernel/kernel.h" |
| 12 | #include "core/hle/kernel/process.h" | 13 | #include "core/hle/kernel/process.h" |
| 13 | #include "core/hle/kernel/resource_limit.h" | 14 | #include "core/hle/kernel/resource_limit.h" |
| 15 | #include "core/hle/kernel/scheduler.h" | ||
| 14 | #include "core/hle/kernel/thread.h" | 16 | #include "core/hle/kernel/thread.h" |
| 15 | #include "core/hle/kernel/vm_manager.h" | 17 | #include "core/hle/kernel/vm_manager.h" |
| 16 | #include "core/memory.h" | 18 | #include "core/memory.h" |
| @@ -128,6 +130,91 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { | |||
| 128 | Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this); | 130 | Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this); |
| 129 | } | 131 | } |
| 130 | 132 | ||
| 133 | void Process::PrepareForTermination() { | ||
| 134 | status = ProcessStatus::Exited; | ||
| 135 | |||
| 136 | const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) { | ||
| 137 | for (auto& thread : thread_list) { | ||
| 138 | if (thread->owner_process != this) | ||
| 139 | continue; | ||
| 140 | |||
| 141 | if (thread == GetCurrentThread()) | ||
| 142 | continue; | ||
| 143 | |||
| 144 | // TODO(Subv): When are the other running/ready threads terminated? | ||
| 145 | ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny || | ||
| 146 | thread->status == ThreadStatus::WaitSynchAll, | ||
| 147 | "Exiting processes with non-waiting threads is currently unimplemented"); | ||
| 148 | |||
| 149 | thread->Stop(); | ||
| 150 | } | ||
| 151 | }; | ||
| 152 | |||
| 153 | auto& system = Core::System::GetInstance(); | ||
| 154 | stop_threads(system.Scheduler(0)->GetThreadList()); | ||
| 155 | stop_threads(system.Scheduler(1)->GetThreadList()); | ||
| 156 | stop_threads(system.Scheduler(2)->GetThreadList()); | ||
| 157 | stop_threads(system.Scheduler(3)->GetThreadList()); | ||
| 158 | } | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Finds a free location for the TLS section of a thread. | ||
| 162 | * @param tls_slots The TLS page array of the thread's owner process. | ||
| 163 | * Returns a tuple of (page, slot, alloc_needed) where: | ||
| 164 | * page: The index of the first allocated TLS page that has free slots. | ||
| 165 | * slot: The index of the first free slot in the indicated page. | ||
| 166 | * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full). | ||
| 167 | */ | ||
| 168 | static std::tuple<std::size_t, std::size_t, bool> FindFreeThreadLocalSlot( | ||
| 169 | const std::vector<std::bitset<8>>& tls_slots) { | ||
| 170 | // Iterate over all the allocated pages, and try to find one where not all slots are used. | ||
| 171 | for (std::size_t page = 0; page < tls_slots.size(); ++page) { | ||
| 172 | const auto& page_tls_slots = tls_slots[page]; | ||
| 173 | if (!page_tls_slots.all()) { | ||
| 174 | // We found a page with at least one free slot, find which slot it is | ||
| 175 | for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) { | ||
| 176 | if (!page_tls_slots.test(slot)) { | ||
| 177 | return std::make_tuple(page, slot, false); | ||
| 178 | } | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | return std::make_tuple(0, 0, true); | ||
| 184 | } | ||
| 185 | |||
| 186 | VAddr Process::MarkNextAvailableTLSSlotAsUsed(Thread& thread) { | ||
| 187 | auto [available_page, available_slot, needs_allocation] = FindFreeThreadLocalSlot(tls_slots); | ||
| 188 | |||
| 189 | if (needs_allocation) { | ||
| 190 | tls_slots.emplace_back(0); // The page is completely available at the start | ||
| 191 | available_page = tls_slots.size() - 1; | ||
| 192 | available_slot = 0; // Use the first slot in the new page | ||
| 193 | |||
| 194 | // Allocate some memory from the end of the linear heap for this region. | ||
| 195 | auto& tls_memory = thread.GetTLSMemory(); | ||
| 196 | tls_memory->insert(tls_memory->end(), Memory::PAGE_SIZE, 0); | ||
| 197 | |||
| 198 | vm_manager.RefreshMemoryBlockMappings(tls_memory.get()); | ||
| 199 | |||
| 200 | vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, | ||
| 201 | tls_memory, 0, Memory::PAGE_SIZE, MemoryState::ThreadLocal); | ||
| 202 | } | ||
| 203 | |||
| 204 | tls_slots[available_page].set(available_slot); | ||
| 205 | |||
| 206 | return Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE + | ||
| 207 | available_slot * Memory::TLS_ENTRY_SIZE; | ||
| 208 | } | ||
| 209 | |||
| 210 | void Process::FreeTLSSlot(VAddr tls_address) { | ||
| 211 | const VAddr tls_base = tls_address - Memory::TLS_AREA_VADDR; | ||
| 212 | const VAddr tls_page = tls_base / Memory::PAGE_SIZE; | ||
| 213 | const VAddr tls_slot = (tls_base % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; | ||
| 214 | |||
| 215 | tls_slots[tls_page].reset(tls_slot); | ||
| 216 | } | ||
| 217 | |||
| 131 | void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { | 218 | void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { |
| 132 | const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, | 219 | const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions, |
| 133 | MemoryState memory_state) { | 220 | MemoryState memory_state) { |
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 81538f70c..04d74e572 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h | |||
| @@ -131,6 +131,16 @@ public: | |||
| 131 | return HANDLE_TYPE; | 131 | return HANDLE_TYPE; |
| 132 | } | 132 | } |
| 133 | 133 | ||
| 134 | /// Gets the current status of the process | ||
| 135 | ProcessStatus GetStatus() const { | ||
| 136 | return status; | ||
| 137 | } | ||
| 138 | |||
| 139 | /// Gets the unique ID that identifies this particular process. | ||
| 140 | u32 GetProcessID() const { | ||
| 141 | return process_id; | ||
| 142 | } | ||
| 143 | |||
| 134 | /// Title ID corresponding to the process | 144 | /// Title ID corresponding to the process |
| 135 | u64 program_id; | 145 | u64 program_id; |
| 136 | 146 | ||
| @@ -154,11 +164,6 @@ public: | |||
| 154 | u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK; | 164 | u32 allowed_processor_mask = THREADPROCESSORID_DEFAULT_MASK; |
| 155 | u32 allowed_thread_priority_mask = 0xFFFFFFFF; | 165 | u32 allowed_thread_priority_mask = 0xFFFFFFFF; |
| 156 | u32 is_virtual_address_memory_enabled = 0; | 166 | u32 is_virtual_address_memory_enabled = 0; |
| 157 | /// Current status of the process | ||
| 158 | ProcessStatus status; | ||
| 159 | |||
| 160 | /// The ID of this process | ||
| 161 | u32 process_id = 0; | ||
| 162 | 167 | ||
| 163 | /** | 168 | /** |
| 164 | * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them | 169 | * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them |
| @@ -171,13 +176,42 @@ public: | |||
| 171 | */ | 176 | */ |
| 172 | void Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size); | 177 | void Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size); |
| 173 | 178 | ||
| 179 | /** | ||
| 180 | * Prepares a process for termination by stopping all of its threads | ||
| 181 | * and clearing any other resources. | ||
| 182 | */ | ||
| 183 | void PrepareForTermination(); | ||
| 184 | |||
| 174 | void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr); | 185 | void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr); |
| 175 | 186 | ||
| 176 | /////////////////////////////////////////////////////////////////////////////////////////////// | 187 | /////////////////////////////////////////////////////////////////////////////////////////////// |
| 177 | // Memory Management | 188 | // Memory Management |
| 178 | 189 | ||
| 190 | // Marks the next available region as used and returns the address of the slot. | ||
| 191 | VAddr MarkNextAvailableTLSSlotAsUsed(Thread& thread); | ||
| 192 | |||
| 193 | // Frees a used TLS slot identified by the given address | ||
| 194 | void FreeTLSSlot(VAddr tls_address); | ||
| 195 | |||
| 196 | ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); | ||
| 197 | ResultCode HeapFree(VAddr target, u32 size); | ||
| 198 | |||
| 199 | ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size); | ||
| 200 | |||
| 201 | ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); | ||
| 202 | |||
| 179 | VMManager vm_manager; | 203 | VMManager vm_manager; |
| 180 | 204 | ||
| 205 | private: | ||
| 206 | explicit Process(KernelCore& kernel); | ||
| 207 | ~Process() override; | ||
| 208 | |||
| 209 | /// Current status of the process | ||
| 210 | ProcessStatus status; | ||
| 211 | |||
| 212 | /// The ID of this process | ||
| 213 | u32 process_id = 0; | ||
| 214 | |||
| 181 | // Memory used to back the allocations in the regular heap. A single vector is used to cover | 215 | // Memory used to back the allocations in the regular heap. A single vector is used to cover |
| 182 | // the entire virtual address space extents that bound the allocations, including any holes. | 216 | // the entire virtual address space extents that bound the allocations, including any holes. |
| 183 | // This makes deallocation and reallocation of holes fast and keeps process memory contiguous | 217 | // This makes deallocation and reallocation of holes fast and keeps process memory contiguous |
| @@ -197,17 +231,6 @@ public: | |||
| 197 | std::vector<std::bitset<8>> tls_slots; | 231 | std::vector<std::bitset<8>> tls_slots; |
| 198 | 232 | ||
| 199 | std::string name; | 233 | std::string name; |
| 200 | |||
| 201 | ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); | ||
| 202 | ResultCode HeapFree(VAddr target, u32 size); | ||
| 203 | |||
| 204 | ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size); | ||
| 205 | |||
| 206 | ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); | ||
| 207 | |||
| 208 | private: | ||
| 209 | explicit Process(KernelCore& kernel); | ||
| 210 | ~Process() override; | ||
| 211 | }; | 234 | }; |
| 212 | 235 | ||
| 213 | } // namespace Kernel | 236 | } // namespace Kernel |
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 371fc439e..0bc407098 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp | |||
| @@ -169,7 +169,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) { | |||
| 169 | return ERR_INVALID_HANDLE; | 169 | return ERR_INVALID_HANDLE; |
| 170 | } | 170 | } |
| 171 | 171 | ||
| 172 | *process_id = process->process_id; | 172 | *process_id = process->GetProcessID(); |
| 173 | return RESULT_SUCCESS; | 173 | return RESULT_SUCCESS; |
| 174 | } | 174 | } |
| 175 | 175 | ||
| @@ -530,35 +530,13 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd | |||
| 530 | 530 | ||
| 531 | /// Exits the current process | 531 | /// Exits the current process |
| 532 | static void ExitProcess() { | 532 | static void ExitProcess() { |
| 533 | LOG_INFO(Kernel_SVC, "Process {} exiting", Core::CurrentProcess()->process_id); | 533 | auto& current_process = Core::CurrentProcess(); |
| 534 | 534 | ||
| 535 | ASSERT_MSG(Core::CurrentProcess()->status == ProcessStatus::Running, | 535 | LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID()); |
| 536 | ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running, | ||
| 536 | "Process has already exited"); | 537 | "Process has already exited"); |
| 537 | 538 | ||
| 538 | Core::CurrentProcess()->status = ProcessStatus::Exited; | 539 | current_process->PrepareForTermination(); |
| 539 | |||
| 540 | auto stop_threads = [](const std::vector<SharedPtr<Thread>>& thread_list) { | ||
| 541 | for (auto& thread : thread_list) { | ||
| 542 | if (thread->owner_process != Core::CurrentProcess()) | ||
| 543 | continue; | ||
| 544 | |||
| 545 | if (thread == GetCurrentThread()) | ||
| 546 | continue; | ||
| 547 | |||
| 548 | // TODO(Subv): When are the other running/ready threads terminated? | ||
| 549 | ASSERT_MSG(thread->status == ThreadStatus::WaitSynchAny || | ||
| 550 | thread->status == ThreadStatus::WaitSynchAll, | ||
| 551 | "Exiting processes with non-waiting threads is currently unimplemented"); | ||
| 552 | |||
| 553 | thread->Stop(); | ||
| 554 | } | ||
| 555 | }; | ||
| 556 | |||
| 557 | auto& system = Core::System::GetInstance(); | ||
| 558 | stop_threads(system.Scheduler(0)->GetThreadList()); | ||
| 559 | stop_threads(system.Scheduler(1)->GetThreadList()); | ||
| 560 | stop_threads(system.Scheduler(2)->GetThreadList()); | ||
| 561 | stop_threads(system.Scheduler(3)->GetThreadList()); | ||
| 562 | 540 | ||
| 563 | // Kill the current thread | 541 | // Kill the current thread |
| 564 | GetCurrentThread()->Stop(); | 542 | GetCurrentThread()->Stop(); |
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index c2d7535c9..315f65338 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp | |||
| @@ -65,10 +65,7 @@ void Thread::Stop() { | |||
| 65 | wait_objects.clear(); | 65 | wait_objects.clear(); |
| 66 | 66 | ||
| 67 | // Mark the TLS slot in the thread's page as free. | 67 | // Mark the TLS slot in the thread's page as free. |
| 68 | const u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE; | 68 | owner_process->FreeTLSSlot(tls_address); |
| 69 | const u64 tls_slot = | ||
| 70 | ((tls_address - Memory::TLS_AREA_VADDR) % Memory::PAGE_SIZE) / Memory::TLS_ENTRY_SIZE; | ||
| 71 | Core::CurrentProcess()->tls_slots[tls_page].reset(tls_slot); | ||
| 72 | } | 69 | } |
| 73 | 70 | ||
| 74 | void WaitCurrentThread_Sleep() { | 71 | void WaitCurrentThread_Sleep() { |
| @@ -178,32 +175,6 @@ void Thread::ResumeFromWait() { | |||
| 178 | } | 175 | } |
| 179 | 176 | ||
| 180 | /** | 177 | /** |
| 181 | * Finds a free location for the TLS section of a thread. | ||
| 182 | * @param tls_slots The TLS page array of the thread's owner process. | ||
| 183 | * Returns a tuple of (page, slot, alloc_needed) where: | ||
| 184 | * page: The index of the first allocated TLS page that has free slots. | ||
| 185 | * slot: The index of the first free slot in the indicated page. | ||
| 186 | * alloc_needed: Whether there's a need to allocate a new TLS page (All pages are full). | ||
| 187 | */ | ||
| 188 | static std::tuple<std::size_t, std::size_t, bool> GetFreeThreadLocalSlot( | ||
| 189 | const std::vector<std::bitset<8>>& tls_slots) { | ||
| 190 | // Iterate over all the allocated pages, and try to find one where not all slots are used. | ||
| 191 | for (std::size_t page = 0; page < tls_slots.size(); ++page) { | ||
| 192 | const auto& page_tls_slots = tls_slots[page]; | ||
| 193 | if (!page_tls_slots.all()) { | ||
| 194 | // We found a page with at least one free slot, find which slot it is | ||
| 195 | for (std::size_t slot = 0; slot < page_tls_slots.size(); ++slot) { | ||
| 196 | if (!page_tls_slots.test(slot)) { | ||
| 197 | return std::make_tuple(page, slot, false); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | return std::make_tuple(0, 0, true); | ||
| 204 | } | ||
| 205 | |||
| 206 | /** | ||
| 207 | * Resets a thread context, making it ready to be scheduled and run by the CPU | 178 | * Resets a thread context, making it ready to be scheduled and run by the CPU |
| 208 | * @param context Thread context to reset | 179 | * @param context Thread context to reset |
| 209 | * @param stack_top Address of the top of the stack | 180 | * @param stack_top Address of the top of the stack |
| @@ -264,32 +235,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name | |||
| 264 | thread->owner_process = owner_process; | 235 | thread->owner_process = owner_process; |
| 265 | thread->scheduler = Core::System::GetInstance().Scheduler(processor_id); | 236 | thread->scheduler = Core::System::GetInstance().Scheduler(processor_id); |
| 266 | thread->scheduler->AddThread(thread, priority); | 237 | thread->scheduler->AddThread(thread, priority); |
| 267 | 238 | thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread); | |
| 268 | // Find the next available TLS index, and mark it as used | ||
| 269 | auto& tls_slots = owner_process->tls_slots; | ||
| 270 | |||
| 271 | auto [available_page, available_slot, needs_allocation] = GetFreeThreadLocalSlot(tls_slots); | ||
| 272 | if (needs_allocation) { | ||
| 273 | tls_slots.emplace_back(0); // The page is completely available at the start | ||
| 274 | available_page = tls_slots.size() - 1; | ||
| 275 | available_slot = 0; // Use the first slot in the new page | ||
| 276 | |||
| 277 | // Allocate some memory from the end of the linear heap for this region. | ||
| 278 | const std::size_t offset = thread->tls_memory->size(); | ||
| 279 | thread->tls_memory->insert(thread->tls_memory->end(), Memory::PAGE_SIZE, 0); | ||
| 280 | |||
| 281 | auto& vm_manager = owner_process->vm_manager; | ||
| 282 | vm_manager.RefreshMemoryBlockMappings(thread->tls_memory.get()); | ||
| 283 | |||
| 284 | vm_manager.MapMemoryBlock(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE, | ||
| 285 | thread->tls_memory, 0, Memory::PAGE_SIZE, | ||
| 286 | MemoryState::ThreadLocal); | ||
| 287 | } | ||
| 288 | |||
| 289 | // Mark the slot as used | ||
| 290 | tls_slots[available_page].set(available_slot); | ||
| 291 | thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE + | ||
| 292 | available_slot * Memory::TLS_ENTRY_SIZE; | ||
| 293 | 239 | ||
| 294 | // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used | 240 | // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used |
| 295 | // to initialize the context | 241 | // to initialize the context |
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 91e9b79ec..4250144c3 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h | |||
| @@ -62,6 +62,9 @@ enum class ThreadWakeupReason { | |||
| 62 | 62 | ||
| 63 | class Thread final : public WaitObject { | 63 | class Thread final : public WaitObject { |
| 64 | public: | 64 | public: |
| 65 | using TLSMemory = std::vector<u8>; | ||
| 66 | using TLSMemoryPtr = std::shared_ptr<TLSMemory>; | ||
| 67 | |||
| 65 | /** | 68 | /** |
| 66 | * Creates and returns a new thread. The new thread is immediately scheduled | 69 | * Creates and returns a new thread. The new thread is immediately scheduled |
| 67 | * @param kernel The kernel instance this thread will be created under. | 70 | * @param kernel The kernel instance this thread will be created under. |
| @@ -134,6 +137,14 @@ public: | |||
| 134 | return thread_id; | 137 | return thread_id; |
| 135 | } | 138 | } |
| 136 | 139 | ||
| 140 | TLSMemoryPtr& GetTLSMemory() { | ||
| 141 | return tls_memory; | ||
| 142 | } | ||
| 143 | |||
| 144 | const TLSMemoryPtr& GetTLSMemory() const { | ||
| 145 | return tls_memory; | ||
| 146 | } | ||
| 147 | |||
| 137 | /** | 148 | /** |
| 138 | * Resumes a thread from waiting | 149 | * Resumes a thread from waiting |
| 139 | */ | 150 | */ |
| @@ -269,7 +280,7 @@ private: | |||
| 269 | explicit Thread(KernelCore& kernel); | 280 | explicit Thread(KernelCore& kernel); |
| 270 | ~Thread() override; | 281 | ~Thread() override; |
| 271 | 282 | ||
| 272 | std::shared_ptr<std::vector<u8>> tls_memory = std::make_shared<std::vector<u8>>(); | 283 | TLSMemoryPtr tls_memory = std::make_shared<TLSMemory>(); |
| 273 | }; | 284 | }; |
| 274 | 285 | ||
| 275 | /** | 286 | /** |