Skip to content

Commit e94075e

Browse files
committed
fix: ensure Python backend shm cleanup on forced shutdown
Register parent-owned shm regions for atexit cleanup, remove regions explicitly in TerminateStub, and honor stub-timeout-seconds during stub teardown to avoid orphaned regions when server exit times out.
1 parent 4b3337c commit e94075e

4 files changed

Lines changed: 103 additions & 5 deletions

File tree

src/shm_manager.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,54 @@
2626

2727
#include "shm_manager.h"
2828

29+
#include <atomic>
2930
#include <boost/interprocess/managed_external_buffer.hpp>
3031
#include <boost/interprocess/mapped_region.hpp>
3132
#include <boost/interprocess/shared_memory_object.hpp>
33+
#include <cstdlib>
3234
#include <iostream>
35+
#include <mutex>
36+
#include <unordered_set>
3337

3438
namespace triton { namespace backend { namespace python {
3539

40+
namespace {
41+
42+
std::mutex parent_shm_regions_mu;
43+
std::unordered_set<std::string> parent_shm_regions;
44+
std::atomic<bool> parent_shm_atexit_registered{false};
45+
46+
void
47+
CleanupParentShmRegions()
48+
{
49+
std::lock_guard<std::mutex> lock(parent_shm_regions_mu);
50+
for (const auto& region : parent_shm_regions) {
51+
bi::shared_memory_object::remove(region.c_str());
52+
}
53+
parent_shm_regions.clear();
54+
}
55+
56+
void
57+
RegisterParentShmRegion(const std::string& shm_region_name)
58+
{
59+
{
60+
std::lock_guard<std::mutex> lock(parent_shm_regions_mu);
61+
parent_shm_regions.insert(shm_region_name);
62+
}
63+
if (!parent_shm_atexit_registered.exchange(true)) {
64+
std::atexit(CleanupParentShmRegions);
65+
}
66+
}
67+
68+
void
69+
UnregisterParentShmRegion(const std::string& shm_region_name)
70+
{
71+
std::lock_guard<std::mutex> lock(parent_shm_regions_mu);
72+
parent_shm_regions.erase(shm_region_name);
73+
}
74+
75+
} // namespace
76+
3677
void
3778
CUDAMemoryPoolManager::SetCUDAPoolAddress(
3879
const int32_t device_id, void* cuda_pool_address)
@@ -139,6 +180,7 @@ SharedMemoryManager::SharedMemoryManager(
139180
if (create) {
140181
*total_size_ = current_capacity_;
141182
new (shm_mutex_) bi::interprocess_mutex;
183+
RegisterParentShmRegion(shm_region_name_);
142184
}
143185
}
144186

@@ -226,9 +268,17 @@ SharedMemoryManager::FreeMemory()
226268

227269

228270
SharedMemoryManager::~SharedMemoryManager() noexcept(false)
271+
{
272+
RemoveShmRegion();
273+
}
274+
275+
void
276+
SharedMemoryManager::RemoveShmRegion()
229277
{
230278
if (delete_region_) {
231279
bi::shared_memory_object::remove(shm_region_name_.c_str());
280+
UnregisterParentShmRegion(shm_region_name_);
281+
delete_region_ = false;
232282
}
233283
}
234284

src/shm_manager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,9 @@ class SharedMemoryManager {
194194

195195
void SetDeleteRegion(bool delete_region);
196196

197+
// Remove the parent-owned shared memory region from the filesystem.
198+
void RemoveShmRegion();
199+
197200
std::unique_ptr<CUDAMemoryPoolManager>& GetCUDAMemoryPoolManager()
198201
{
199202
return cuda_memory_pool_manager_;

src/stub_launcher.cc

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace triton { namespace backend { namespace python {
4242
StubLauncher::StubLauncher(const std::string stub_process_kind)
4343
: parent_pid_(0), is_initialized_(false),
4444
stub_process_kind_(stub_process_kind), model_instance_name_(""),
45-
device_id_(0), kind_("")
45+
device_id_(0), kind_(""), stub_timeout_seconds_(30)
4646
{
4747
}
4848

@@ -51,7 +51,7 @@ StubLauncher::StubLauncher(
5151
const int32_t device_id, const std::string kind)
5252
: is_initialized_(false), stub_process_kind_(stub_process_kind),
5353
model_instance_name_(model_instance_name), device_id_(device_id),
54-
kind_(kind)
54+
kind_(kind), stub_timeout_seconds_(30)
5555
{
5656
}
5757

@@ -64,6 +64,8 @@ StubLauncher::Initialize(ModelState* model_state)
6464
shm_growth_byte_size_ = model_state->StateForBackend()->shm_growth_byte_size;
6565
shm_message_queue_size_ =
6666
model_state->StateForBackend()->shm_message_queue_size;
67+
stub_timeout_seconds_ =
68+
model_state->StateForBackend()->stub_timeout_seconds;
6769
python_execution_env_ = model_state->PythonExecutionEnv();
6870
python_lib_ = model_state->StateForBackend()->python_lib;
6971
model_state->ModelConfig().Write(&model_config_buffer_);
@@ -803,13 +805,18 @@ StubLauncher::TerminateStub()
803805
if (is_initialized_) {
804806
bool force_kill = false;
805807
if (is_healthy_) {
808+
const int64_t timeout_ms = stub_timeout_seconds_ * 1000;
806809
// Finalize command does not have any arguments.
807810
std::unique_ptr<IPCMessage> ipc_message =
808811
IPCMessage::Create(shm_pool_, false /* inline_response */);
809812

810813
ipc_message->Command() = PYTHONSTUB_FinalizeRequest;
811814
stub_message_queue_->Push(ipc_message->ShmHandle());
812-
parent_message_queue_->Pop();
815+
bool success = false;
816+
parent_message_queue_->Pop(timeout_ms, success);
817+
if (!success) {
818+
force_kill = true;
819+
}
813820

814821
stub_message_queue_.reset();
815822
parent_message_queue_.reset();
@@ -820,11 +827,15 @@ StubLauncher::TerminateStub()
820827

821828
if (force_kill) {
822829
KillStubProcess();
823-
} else {
824-
WaitForStubProcess();
830+
} else if (!WaitForStubProcessWithTimeout(stub_timeout_seconds_)) {
831+
KillStubProcess();
825832
}
826833
}
827834

835+
if (shm_pool_ != nullptr) {
836+
shm_pool_->RemoveShmRegion();
837+
}
838+
828839
// First destroy the IPCControl. This makes sure that IPCControl is
829840
// destroyed before the shared memory manager goes out of scope.
830841
ipc_control_.reset();
@@ -924,10 +935,40 @@ StubLauncher::WaitForStubProcess()
924935
// Added this check to ensure server doesn't hang waiting after stub
925936
// process has already be killed and cannot be waited on
926937
waitpid(stub_pid_, &status, 0);
938+
stub_pid_ = 0;
927939
}
928940
#endif
929941
}
930942

943+
bool
944+
StubLauncher::WaitForStubProcessWithTimeout(int64_t timeout_seconds)
945+
{
946+
#ifdef _WIN32
947+
WaitForStubProcess();
948+
return true;
949+
#else
950+
if (stub_pid_ == 0) {
951+
return true;
952+
}
953+
954+
for (int64_t elapsed = 0; elapsed < timeout_seconds; ++elapsed) {
955+
int status;
956+
pid_t ret = waitpid(stub_pid_, &status, WNOHANG);
957+
if (ret == stub_pid_) {
958+
stub_pid_ = 0;
959+
return true;
960+
}
961+
if (ret == -1) {
962+
stub_pid_ = 0;
963+
return true;
964+
}
965+
sleep(1);
966+
}
967+
968+
return false;
969+
#endif
970+
}
971+
931972
#ifdef TRITON_ENABLE_GPU
932973
void
933974
StubLauncher::ShareCUDAMemoryPool(

src/stub_launcher.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ class StubLauncher {
161161
// Wait for stub process
162162
void WaitForStubProcess();
163163

164+
// Wait for stub process with timeout. Returns true if the stub exited.
165+
bool WaitForStubProcessWithTimeout(int64_t timeout_seconds);
166+
164167
#ifndef _WIN32
165168
// FIXME [DLIS-5969]: Enable for Windows when custom execution environments
166169
// are supported.
@@ -199,6 +202,7 @@ class StubLauncher {
199202
int64_t shm_default_byte_size_;
200203
int64_t shm_growth_byte_size_;
201204
int64_t shm_message_queue_size_;
205+
int64_t stub_timeout_seconds_;
202206

203207
// Path to python execution environment
204208
std::string path_to_libpython_;

0 commit comments

Comments
 (0)