From e95d4a57bce8c4af57eee2dc3ecfcf8b3edc1a0b Mon Sep 17 00:00:00 2001 From: maj00r Date: Sun, 6 Sep 2026 13:48:05 +0200 Subject: [PATCH] Disown the vehicles before deleting their driver. TDynamicObject::Update() reads ctOwner->Controlling() every frame, but ctOwner is only ever refreshed by CheckVehicles() and cleared only by Dettach(). Both TController::MoveTo() and ~TDynamicObject() delete a controller while the rest of the consist still points at it, so the vehicles keep dereferencing freed memory until some other driver happens to claim them. Seen as a segfault in DynObj.cpp on ctOwner->Controlling()->MainCtrlPosNo after changing cabs through an EN57 with an AI driver in the leading unit: ctOwner was a live-looking pointer into a freed TController, its mvControlling long gone. ~TController() now clears ctOwner on every vehicle still pointing at it. nullptr is what the readers already expect - they fall back on Mechanik or skip the block - and the next CheckVehicles() re-establishes ownership. --- vehicle/Driver.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/vehicle/Driver.cpp b/vehicle/Driver.cpp index 18255a6f..7e53ce94 100644 --- a/vehicle/Driver.cpp +++ b/vehicle/Driver.cpp @@ -1999,6 +1999,19 @@ void TController::CloseLog() TController::~TController() { // wykopanie mechanika z roboty + // ctOwner is refreshed only by CheckVehicles(), so a vehicle left pointing at a deleted driver + // keeps being dereferenced every frame until some other one claims it. Disown them here, as + // MoveTo() and ~TDynamicObject() both delete a controller out from under a live consist. + // nullptr is expected by the readers, which fall back on Mechanik or skip the block outright + if( simulation::is_ready + && false == Global.applicationQuitOrder ) { + for( auto *vehicle : simulation::Vehicles.sequence() ) { + if( vehicle != nullptr + && vehicle->ctOwner == this ) { + vehicle->ctOwner = nullptr; + } + } + } CloseLog(); };