[mapguide-commits] r10233 - in branches/4.0/MgDev: Common/Foundation/System Common/Geometry/CoordinateSystem Common/MdfParser Oem/geos/include/geos/geomgraph Oem/geos/src/geomgraph Oem/geos/src/operation/overlay Server/src/Common/Thread Server/src/Core Server/src/Services/Feature Server/src/Services/Kml Server/src/Services/Tile Server/src/UnitTesting

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Fri Oct 30 03:42:01 PDT 2026


Author: jng
Date: 2026-10-30 03:41:59 -0700 (Fri, 30 Oct 2026)
New Revision: 10233

Modified:
   branches/4.0/MgDev/Common/Foundation/System/XmlUtil.cpp
   branches/4.0/MgDev/Common/Geometry/CoordinateSystem/CoordinateSystemFactory.cpp
   branches/4.0/MgDev/Common/MdfParser/IOGridColorBands.cpp
   branches/4.0/MgDev/Oem/geos/include/geos/geomgraph/EdgeRing.h
   branches/4.0/MgDev/Oem/geos/src/geomgraph/EdgeRing.cpp
   branches/4.0/MgDev/Oem/geos/src/operation/overlay/MaximalEdgeRing.cpp
   branches/4.0/MgDev/Oem/geos/src/operation/overlay/MinimalEdgeRing.cpp
   branches/4.0/MgDev/Oem/geos/src/operation/overlay/PolygonBuilder.cpp
   branches/4.0/MgDev/Server/src/Common/Thread/ThreadBase.cpp
   branches/4.0/MgDev/Server/src/Core/Server.cpp
   branches/4.0/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp
   branches/4.0/MgDev/Server/src/Services/Kml/ServerKmlService.cpp
   branches/4.0/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp
   branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp
   branches/4.0/MgDev/Server/src/UnitTesting/TestTileService.cpp
Log:
Plug the leaks reported by mgserver test all on Linux

The Debug `mgserver test all` run reported 647,644 bytes in 1,587 allocations - 27 direct and
216 indirect records - and now produces no LeakSanitizer report at all. Most of the records
were a counted reference dropped on the line that used it; the rest were process-lifetime
state that only a teardown can reach, and one was a thread that outlived the wait intended to
stop it. 610 cases ran with 608 passing before and after (the two failures are TestLogManager
cases, unchanged by this work).

  * Common/Foundation/System/XmlUtil.cpp: MgXmlUtil::ToBytes() released the serializer and the
    format target but not the DOMLSOutput that createLSOutput() hands to its caller, which is
    9,120 B in 190 records carrying no frames of their own (XMemory::operator new). It is
    released now.

  * Common/Geometry/CoordinateSystem/CoordinateSystemFactory.cpp and Server/src/Core/Server.cpp:
    EnumerateCoordinateSystems makes CS-Map read the whole Category.CSD into process globals -
    CSrdCategoryEx, 265,536 B in 245 records - and only CSrlsCategories() releases them. The
    catalog itself is not destroyed at shutdown, because live coordinate system objects still
    hold 22 references to it, and CS-Map is compiled without ASan, so its C globals are not LSan
    roots: a cache still resident at teardown reads as leaked. MgCoordinateSystemFactory::
    DeleteCatalog() - which already existed - now calls CSrlsCategories(), and MgServer::fini()
    calls it in the "orderly clean up applicable static manager objects" block. Freeing it only
    at static destruction would not have helped: LSan's check is an atexit handler and runs
    before __cxa_finalize.

  * Common/MdfParser/IOGridColorBands.cpp: StartElement() creates one ChannelBand per
    <RedBand>/<GreenBand>/<BlueBand> and EndElement() only copies their values into the
    GridColorBands it adopts, so nothing else ever owned them - 2,592 B in 27 records. Deleted
    in ~IOGridColorBands.

  * Oem/geos/src/operation/overlay/{PolygonBuilder,MaximalEdgeRing,MinimalEdgeRing}.cpp and
    Oem/geos/src/geomgraph/EdgeRing.cpp (declaration in include/geos/geomgraph/EdgeRing.h): a
    TopologyException escaping PolygonBuilder::add() left maxEdgeRings, edgeRings and
    freeHoleList behind - 4,800 B direct and about 15 KB indirect - and SnapIfNeededOverlayOp
    retries the operation with snapped inputs, so a *passing* geometry suite was leaking the
    partial result of every failed attempt. add(), buildMaximalEdgeRings() and
    buildMinimalEdgeRings() release what they built before rethrowing, with each ring's
    ownership moved by nulling its vector entry (and resizing maxEdgeRings) so that the
    caller's cleanup cannot delete it a second time. A ring constructor that throws runs no
    destructor either, so EdgeRing::cleanupAfterFailedConstruction() releases the point
    sequence or the ring it was given, and the MaximalEdgeRing/MinimalEdgeRing constructors
    call it from a catch (...) { ...; throw; }.

  * Server/src/Common/Thread/ThreadBase.cpp: MgThreadBase::Activate() spawned its threads with
    THR_DETACHED, and ACE_Task::wait() on a detached thread returns immediately (measured: 0 us,
    against 500 us for a joinable thread doing the same half-second of work), so the waits in
    MgLogManager::StopLogThread, MgServerManager::StopWorkerThreads and MgServer::svc were
    no-ops. main() then called ACE::fini() with the log thread still running, which finalised
    ACE's thread-specific-storage machinery before that thread exited, so its ACE_Log_Msg
    (4,216 B), the log message's 4,097 B buffer and its ACE_Thread_Exit (16 B) were never
    released. The threads are joinable now (THR_NEW_LWP without THR_DETACHED), which makes the
    existing waits actually wait: every pool prints "Exiting thread" before
    MgServer::~MgServer().

  * Server/src/Services/Tile/TileCacheDefault.cpp: MgTileCacheDefault::GetMapFromDefinition()
    passed MgUserInformation::GetCurrentUserInfo() - a counted reference - straight to
    MgSiteConnection::Open(), 4 x 320 B. Held in a Ptr<MgUserInformation> now.

  * Server/src/Services/Kml/ServerKmlService.cpp: GetPointStyleImageUrl() called
    layer->GetLayerDefinition()->ToString(), dropping the definition id's reference (272 B). The
    id is held in a Ptr<MgResourceIdentifier> and reused for the lookup further down, which
    fetched it a second time.

  * Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp: GetIdentityHash() used a raw
    FdoStringElement* for FdoStringCollection::GetItem(), which returns an AddRef'ed element -
    3 x 48 B. It is an FdoPtr now.

  * Server/src/UnitTesting/TestRasterStylization.cpp: PublishFeatureSource() dropped the class
    collection MgFeatureSchema::GetClasses() returns - 1,800 B for the collection and 285 KB
    indirect, being every class definition, property definition and string behind it - and the
    two footprint checks dereferenced GetLowerLeftCoordinate()/GetUpperRightCoordinate() on an
    MgEnvelope inline, for 1,280 B in 10 records. Each is held in a Ptr.

  * Server/src/UnitTesting/TestTileService.cpp: two tests allocated a runtime map with a raw
    MgMap* and never released it. The map was not itself in the report: MgLayerCollection keeps
    a raw back-pointer to its map, so what LSan saw was one cycle in which every chunk was
    reachable from another, and 172 KB of indirect records was the sign of it. Ptr<MgMap> now.

The binding surface does not move. No .i file, MapGuideApiGen.xml or SwigCommon fragment is
touched, MgCoordinateSystemFactory::DeleteCatalog() already existed, and the only header in the
change (Oem/geos/include/geos/geomgraph/EdgeRing.h) is internal GEOS, so there is nothing to
re-cut in the API dumps.

Modified: branches/4.0/MgDev/Common/Foundation/System/XmlUtil.cpp
===================================================================
--- branches/4.0/MgDev/Common/Foundation/System/XmlUtil.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Common/Foundation/System/XmlUtil.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -778,6 +778,11 @@
     MG_XML_CATCH(L"MgXmlUtil.ToBytes")
 
     delete theSerializer;
+    // the output descriptor is owned by the caller of createLSOutput(), not by the serializer
+    if (NULL != theOutputDesc)
+    {
+        theOutputDesc->release();
+    }
     delete memTarget;
 
     MG_XML_THROW()

Modified: branches/4.0/MgDev/Common/Geometry/CoordinateSystem/CoordinateSystemFactory.cpp
===================================================================
--- branches/4.0/MgDev/Common/Geometry/CoordinateSystem/CoordinateSystemFactory.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Common/Geometry/CoordinateSystem/CoordinateSystemFactory.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -40,6 +40,7 @@
 #include "CoordSysGridGeneric.h"        //for CCoordinateSystemGridGeneric
 #include "CoordinateSystemFactory.h"    //for MgCoordinateSystemFactory
 #include "CoordinateSystemCache.h"
+#include "cs_map.h"
 
 using namespace CSLibrary;
 
@@ -180,6 +181,12 @@
         sm_pCatalog->Release();
         sm_pCatalog = NULL;
     }
+
+    // CS-Map keeps the category dictionary it has read in process globals, which only
+    // CSrlsCategories() releases. The catalog above does not necessarily get destroyed here -
+    // live coordinate system objects hold references to it - so release the dictionary
+    // directly; CS-Map re-reads the file if anything asks for a category after this point.
+    CSrlsCategories();
 }
 
 

Modified: branches/4.0/MgDev/Common/MdfParser/IOGridColorBands.cpp
===================================================================
--- branches/4.0/MgDev/Common/MdfParser/IOGridColorBands.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Common/MdfParser/IOGridColorBands.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -52,6 +52,12 @@
 
 IOGridColorBands::~IOGridColorBands()
 {
+    // StartElement() creates one ChannelBand per band element, and EndElement() only copies
+    // their values into the GridColorBands it adopts, so these instances are never owned by
+    // anything else and have to be released here.
+    delete this->m_redChannel;
+    delete this->m_greenChannel;
+    delete this->m_blueChannel;
 }
 
 

Modified: branches/4.0/MgDev/Oem/geos/include/geos/geomgraph/EdgeRing.h
===================================================================
--- branches/4.0/MgDev/Oem/geos/include/geos/geomgraph/EdgeRing.h	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Oem/geos/include/geos/geomgraph/EdgeRing.h	2026-10-30 10:41:59 UTC (rev 10233)
@@ -171,6 +171,14 @@
 
 	void addPoints(Edge *edge, bool isForward, bool isFirstEdge);
 
+	/** \brief
+	 * Release the geometry built so far.
+	 *
+	 * A constructor of a derived class which throws runs no destructor, so the
+	 * point sequence and the ring it created leak unless it releases them here.
+	 */
+	void cleanupAfterFailedConstruction();
+
 	/// a list of EdgeRings which are holes in this EdgeRing
 	std::vector<EdgeRing*> holes;
 

Modified: branches/4.0/MgDev/Oem/geos/src/geomgraph/EdgeRing.cpp
===================================================================
--- branches/4.0/MgDev/Oem/geos/src/geomgraph/EdgeRing.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Oem/geos/src/geomgraph/EdgeRing.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -109,6 +109,23 @@
 #endif
 }
 
+/*protected*/
+void
+EdgeRing::cleanupAfterFailedConstruction()
+{
+	// ownership of [pts] passes to [ring] when the ring is created, see the destructor
+	if ( ring == NULL )
+	{
+		delete pts;
+	}
+	else
+	{
+		delete ring;
+	}
+	pts = NULL;
+	ring = NULL;
+}
+
 bool
 EdgeRing::isIsolated()
 {

Modified: branches/4.0/MgDev/Oem/geos/src/operation/overlay/MaximalEdgeRing.cpp
===================================================================
--- branches/4.0/MgDev/Oem/geos/src/operation/overlay/MaximalEdgeRing.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Oem/geos/src/operation/overlay/MaximalEdgeRing.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -52,8 +52,17 @@
 	:
 	EdgeRing(start, geometryFactory)
 {
-	computePoints(start);
-	computeRing();
+	try
+	{
+		computePoints(start);
+		computeRing();
+	}
+	catch (...)
+	{
+		// a throwing constructor runs no destructor, so release what we built
+		cleanupAfterFailedConstruction();
+		throw;
+	}
 #if GEOS_DEBUG
 	cerr << "MaximalEdgeRing[" << this << "] ctor" << endl;
 #endif

Modified: branches/4.0/MgDev/Oem/geos/src/operation/overlay/MinimalEdgeRing.cpp
===================================================================
--- branches/4.0/MgDev/Oem/geos/src/operation/overlay/MinimalEdgeRing.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Oem/geos/src/operation/overlay/MinimalEdgeRing.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -41,8 +41,17 @@
 	:
 	geomgraph::EdgeRing(start, geometryFactory)
 {
-	computePoints(start);
-	computeRing();
+	try
+	{
+		computePoints(start);
+		computeRing();
+	}
+	catch (...)
+	{
+		// a throwing constructor runs no destructor, so release what we built
+		cleanupAfterFailedConstruction();
+		throw;
+	}
 #if GEOS_DEBUG
 	std::cerr << "MinimalEdgeRing[" << this << "] ctor" << std::endl;
 #endif

Modified: branches/4.0/MgDev/Oem/geos/src/operation/overlay/PolygonBuilder.cpp
===================================================================
--- branches/4.0/MgDev/Oem/geos/src/operation/overlay/PolygonBuilder.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Oem/geos/src/operation/overlay/PolygonBuilder.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -108,16 +108,39 @@
 	PlanarGraph::linkResultDirectedEdges(nodes->begin(), nodes->end());
 
 	vector<MaximalEdgeRing*> maxEdgeRings;
-	buildMaximalEdgeRings(dirEdges, maxEdgeRings);
-
 	vector<EdgeRing*> freeHoleList;
 	vector<MaximalEdgeRing*> edgeRings;
-	buildMinimalEdgeRings(maxEdgeRings, shellList, freeHoleList, edgeRings);
 
-	sortShellsAndHoles(edgeRings, shellList, freeHoleList);
+	// A TopologyException thrown here is not fatal: SnapIfNeededOverlayOp retries the
+	// operation with snapped inputs. The rings built for the failed attempt are not owned
+	// by anything the caller can see, so they have to be released before the exception
+	// leaves this function - otherwise every retried overlay leaks its partial result.
+	try
+	{
+		buildMaximalEdgeRings(dirEdges, maxEdgeRings);
+		buildMinimalEdgeRings(maxEdgeRings, shellList, freeHoleList, edgeRings);
 
-	placeFreeHoles(shellList, freeHoleList);
-	//Assert: every hole on freeHoleList has a shell assigned to it
+		// [sortShellsAndHoles] hands every ring over to one of the two lists
+		sortShellsAndHoles(edgeRings, shellList, freeHoleList);
+		edgeRings.clear();
+
+		placeFreeHoles(shellList, freeHoleList);
+		//Assert: every hole on freeHoleList has a shell assigned to it
+	}
+	catch (...)
+	{
+		for(size_t i=0, n=maxEdgeRings.size(); i<n; ++i)
+			delete maxEdgeRings[i];
+		for(size_t i=0, n=edgeRings.size(); i<n; ++i)
+			delete edgeRings[i];
+		for(size_t i=0, n=freeHoleList.size(); i<n; ++i)
+		{
+			// a hole which has already been assigned to a shell is owned by that shell
+			if (freeHoleList[i]->getShell() == NULL)
+				delete freeHoleList[i];
+		}
+		throw;
+	}
 }
 
 /*public*/
@@ -164,6 +187,9 @@
 	{ // cleanup if that happens (see stmlf-cases-20061020.xml)
 		for(size_t i=oldSize, n=maxEdgeRings.size(); i<n; i++)
 			delete maxEdgeRings[i];
+		// ...and drop them from the vector, so that the caller's own cleanup does not
+		// delete them a second time
+		maxEdgeRings.resize(oldSize);
 		//cerr << "Exception! " << e.what() << endl;
 		throw;
 	}
@@ -188,27 +214,52 @@
 	for(size_t i=0, n=maxEdgeRings.size(); i<n; ++i)
 	{
 		MaximalEdgeRing *er = maxEdgeRings[i];
+		// this ring is owned by this function from here on: [-add()] cleans up
+		// whatever is still in the vector if we throw
+		maxEdgeRings[i] = NULL;
 #if GEOS_DEBUG
-	cerr<<"buildMinimalEdgeRings: maxEdgeRing "<<i<<" has "<<er->getMaxNodeDegree()<<" maxNodeDegree"<<endl;
+		cerr<<"buildMinimalEdgeRings: maxEdgeRing "<<i<<" has "<<er->getMaxNodeDegree()<<" maxNodeDegree"<<endl;
 #endif
 		if (er->getMaxNodeDegree()>2)
 		{
-			er->linkDirectedEdgesForMinimalEdgeRings();
 			vector<MinimalEdgeRing*> minEdgeRings;
-			er->buildMinimalRings(minEdgeRings);
-			// at this point we can go ahead and attempt to place
-			// holes, if this EdgeRing is a polygon
-			EdgeRing *shell=findShell(&minEdgeRings);
-			if(shell != NULL)
+			EdgeRing *shell = NULL;
+			bool shellHandedOver = false;
+			try
 			{
-				placePolygonHoles(shell, &minEdgeRings);
-				newShellList.push_back(shell);
+				er->linkDirectedEdgesForMinimalEdgeRings();
+				er->buildMinimalRings(minEdgeRings);
+				// at this point we can go ahead and attempt to place
+				// holes, if this EdgeRing is a polygon
+				shell=findShell(&minEdgeRings);
+				if(shell != NULL)
+				{
+					placePolygonHoles(shell, &minEdgeRings);
+					newShellList.push_back(shell);
+					shellHandedOver = true;
+				}
+				else
+				{
+					freeHoleList.insert(freeHoleList.end(),
+							minEdgeRings.begin(),
+							minEdgeRings.end() );
+					minEdgeRings.clear(); // now owned by [freeHoleList]
+				}
 			}
-			else
+			catch (...)
 			{
-				freeHoleList.insert(freeHoleList.end(),
-						minEdgeRings.begin(),
-						minEdgeRings.end() );
+				// release the rings this iteration created but has not handed over,
+				// so that they do not outlive the exception
+				for(size_t j=0, m=minEdgeRings.size(); j<m; ++j)
+				{
+					EdgeRing *ring = minEdgeRings[j];
+					// a ring which already has a shell is owned by that shell, and a
+					// shell which is already in [newShellList] is owned by the builder
+					if (ring == shell ? !shellHandedOver : ring->getShell() == NULL)
+						delete ring;
+				}
+				delete er;
+				throw;
 			}
 			delete er;
 		}

Modified: branches/4.0/MgDev/Server/src/Common/Thread/ThreadBase.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/Common/Thread/ThreadBase.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Server/src/Common/Thread/ThreadBase.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -40,7 +40,13 @@
 {
     INT32 nResult = 0;
 
-    if (activate(THR_DETACHED | THR_NEW_LWP, m_nThreads) == -1)
+    // The threads have to be joinable: every shutdown path stops them with an MB_STOP
+    // message and then waits (MgLogManager::StopLogThread, MgServerManager::StopWorkerThreads,
+    // MgServer::svc), and ACE_Task::wait() on a THR_DETACHED thread returns immediately
+    // instead of waiting. A thread which outlives that wait is still running when main()
+    // calls ACE::fini(), which releases ACE's thread-specific-storage machinery - so the
+    // per-thread ACE_Log_Msg/ACE_Thread_Exit of that thread are never freed.
+    if (activate(THR_NEW_LWP, m_nThreads) == -1)
     {
         ACE_ERROR ((LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("MgThreadBase::activate")));
         nResult = -1;

Modified: branches/4.0/MgDev/Server/src/Core/Server.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/Core/Server.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Server/src/Core/Server.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -164,6 +164,11 @@
     MgLoadBalanceManager::Terminate();
     MgLongTransactionManager::Terminate();
 
+    // The coordinate system catalog is a static that holds CS-Map's in-memory dictionaries,
+    // which are plain C globals in a library that is not ASan-instrumented - so they are only
+    // visible to a leak check if they are released here rather than at static destruction.
+    MgCoordinateSystemFactory::DeleteCatalog();
+
     MgLicenseManager* licenseManager = MgLicenseManager::GetInstance();
     ACE_ASSERT(NULL != licenseManager);
     licenseManager->Terminate();

Modified: branches/4.0/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Server/src/Services/Feature/FdoForcedOneToOneFeatureReader.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -250,7 +250,7 @@
         bool append = false;
         wchar_t tbuff[256];
         //Don't bother with IsNull() checks. These are identity property values, how can they be null?
-        FdoStringElement* el = m_idPropNames->GetItem(i);
+        FdoPtr<FdoStringElement> el = m_idPropNames->GetItem(i);
         FdoStringP str = el->GetString();
         FdoString* name = (FdoString*)str;
         

Modified: branches/4.0/MgDev/Server/src/Services/Kml/ServerKmlService.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/Services/Kml/ServerKmlService.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Server/src/Services/Kml/ServerKmlService.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -884,8 +884,11 @@
 
 STRING MgServerKmlService::GetPointStyleImageUrl(STRING agentUri, MgLayer* layer, double scale, CREFSTRING sessionId)
 {
+    // GetLayerDefinition() returns a counted reference: hold it rather than dropping it inline
+    Ptr<MgResourceIdentifier> resId = layer->GetLayerDefinition();
+
     STRING strLegendImage = agentUri + L"?OPERATION=GetLegendImage&LAYERDEFINITION="; 
-    strLegendImage += layer->GetLayerDefinition()->ToString();
+    strLegendImage += resId->ToString();
     strLegendImage += L"&SCALE=" ;
     
     STRING strScale;
@@ -895,7 +898,6 @@
     STRING strGeomType;
     STRING strCategoryIndex = L"-1";
     //get layer definition
-    Ptr<MgResourceIdentifier> resId = layer->GetLayerDefinition();
     unique_ptr<MdfModel::LayerDefinition> ldf(MgLayerBase::GetLayerDefinition(m_svcResource, resId));
 
     MdfModel::VectorLayerDefinition* vl = dynamic_cast<MdfModel::VectorLayerDefinition*>(ldf.get());

Modified: branches/4.0/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Server/src/Services/Tile/TileCacheDefault.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -918,8 +918,11 @@
     {
         // get the service from our helper method
         Ptr<MgResourceService> resourceService = GetResourceServiceForMapDef(mapDefinition, L"MgTileCacheDefault.GetMapFromDefinition");
+        // GetCurrentUserInfo() returns a counted reference, which has to be held rather than
+        // passed on inline: dropping it leaks the caller thread's user information
+        Ptr<MgUserInformation> userInfo = MgUserInformation::GetCurrentUserInfo();
         Ptr<MgSiteConnection> siteConn = new MgSiteConnection();
-        siteConn->Open(MgUserInformation::GetCurrentUserInfo());
+        siteConn->Open(userInfo);
         map = new MgMap(siteConn);
         map->Create(resourceService, mapDefinition, mapString);
         cachedMap = new MgMemoryStreamHelper();

Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -363,7 +363,8 @@
     REQUIRE(schemas->GetCount() > 0);
 
     Ptr<MgFeatureSchema> schema = schemas->GetItem(0);
-    Ptr<MgClassDefinition> klass = schema->GetClasses()->GetItem(0);
+    Ptr<MgClassDefinitionCollection> classes = schema->GetClasses();
+    Ptr<MgClassDefinition> klass = classes->GetItem(0);
 
     Ptr<MgPropertyDefinitionCollection> properties = klass->GetProperties();
     for (INT32 i = 0; i < properties->GetCount() && rasterProperty.empty(); ++i)
@@ -631,10 +632,12 @@
         REQUIRE(GetRasterFootprint(svcFeature, fsId, featureName, rasterProperty,
                                    rasterExtent, imageXSize, imageYSize));
 
-        double minX = rasterExtent->GetLowerLeftCoordinate()->GetX();
-        double minY = rasterExtent->GetLowerLeftCoordinate()->GetY();
-        double maxX = rasterExtent->GetUpperRightCoordinate()->GetX();
-        double maxY = rasterExtent->GetUpperRightCoordinate()->GetY();
+        Ptr<MgCoordinate> lowerLeft = rasterExtent->GetLowerLeftCoordinate();
+        Ptr<MgCoordinate> upperRight = rasterExtent->GetUpperRightCoordinate();
+        double minX = lowerLeft->GetX();
+        double minY = lowerLeft->GetY();
+        double maxX = upperRight->GetX();
+        double maxY = upperRight->GetY();
 
         int factor = (std::max)(imageXSize, imageYSize) / REAL_RENDER_MAX_DIM;
         if (factor < 1)
@@ -779,10 +782,12 @@
     REQUIRE(GetRasterFootprint(svcFeature, fsId, featureName, rasterProperty,
                                rasterExtent, imageXSize, imageYSize));
 
-    double minX = rasterExtent->GetLowerLeftCoordinate()->GetX();
-    double minY = rasterExtent->GetLowerLeftCoordinate()->GetY();
-    double maxX = rasterExtent->GetUpperRightCoordinate()->GetX();
-    double maxY = rasterExtent->GetUpperRightCoordinate()->GetY();
+    Ptr<MgCoordinate> lowerLeft = rasterExtent->GetLowerLeftCoordinate();
+    Ptr<MgCoordinate> upperRight = rasterExtent->GetUpperRightCoordinate();
+    double minX = lowerLeft->GetX();
+    double minY = lowerLeft->GetY();
+    double maxX = upperRight->GetX();
+    double maxY = upperRight->GetY();
 
     STRING csWkt = GetRasterCoordinateSystem(svcFeature, fsId);
     if (csWkt.empty())

Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestTileService.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestTileService.cpp	2026-10-25 09:28:58 UTC (rev 10232)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestTileService.cpp	2026-10-30 10:41:59 UTC (rev 10233)
@@ -1489,7 +1489,7 @@
 
         // make a runtime map
         Ptr<MgResourceIdentifier> tsdres = new MgResourceIdentifier(L"Library://UnitTests/TileSets/XYZ.TileSetDefinition");
-        MgMap* map = new MgMap(m_siteConnection);
+        Ptr<MgMap> map = new MgMap(m_siteConnection);
         map->Create(m_svcResource, tsdres, L"XYZTileSet");
 
         REQUIRE(0 == map->GetFiniteDisplayScaleCount());
@@ -1517,7 +1517,7 @@
 
         // make a runtime map
         Ptr<MgResourceIdentifier> tsdres = new MgResourceIdentifier(L"Library://UnitTests/TileSets/Sheboygan.TileSetDefinition");
-        MgMap* map = new MgMap(m_siteConnection);
+        Ptr<MgMap> map = new MgMap(m_siteConnection);
         map->Create(tsdres, mapName);
 
         Ptr<MgResourceIdentifier> mdfId = map->GetMapDefinition();



More information about the mapguide-commits mailing list