[mapguide-commits] r10219 - in branches/4.0/MgDev: Common/Foundation/System Web/src/HttpHandler Web/src/IsapiAgent Web/src/WebSupport

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Mon Oct 19 09:06:15 PDT 2026


Author: jng
Date: 2026-10-19 09:06:14 -0700 (Mon, 19 Oct 2026)
New Revision: 10219

Modified:
   branches/4.0/MgDev/Common/Foundation/System/Configuration.cpp
   branches/4.0/MgDev/Common/Foundation/System/Configuration.h
   branches/4.0/MgDev/Common/Foundation/System/ConfigurationHeap.cpp
   branches/4.0/MgDev/Web/src/HttpHandler/HttpRequest.cpp
   branches/4.0/MgDev/Web/src/HttpHandler/HttpRequestResponseHandler.cpp
   branches/4.0/MgDev/Web/src/IsapiAgent/IsapiAgent.cpp
   branches/4.0/MgDev/Web/src/WebSupport/InitializeWebTier.cpp
Log:
Fix web tier initialisation failures leaving the process permanently broken

A failed MgInitializeWebTier() call poisoned the process-wide MgConfiguration
singleton for the life of the process.  LoadConfiguration() opens the ACE
configuration heap before importing the file, and ACE_Configuration_Heap::open()
can only succeed once (EBUSY afterwards).  When the import failed the heap was
left open with m_fileLoaded still false, and every later attempt took the EBUSY
branch, which LoadConfiguration() merely logged - reporting success without
loading anything.  MgInitializeWebTierInternal() then carried on with default
values and latched itself as initialised, so only a process restart could clear
the state.

Symptoms this fixes:

  * The MapAgent (ISAPI) failing every request, in MapAgentCommon::LogRequest()
    at MgConfiguration::GetBoolValue(), until the application pool was recycled.
  * "Failed to load the resource file Resources/mapguide_en.res for locale en"
    from every call after a failed first call, because ResourcesPath fell back
    to its default relative path.
  * Coordinate system and resource failures being reported from a web tier that
    claimed to be initialised.

Changes:

  * Common/Foundation/System/Configuration.*: report a failure to open the ACE
    heap as well as a failure to import the file, and add DiscardInstance() so a
    caller can drop an instance whose heap can never be re-opened.

  * Common/Foundation/System/ConfigurationHeap.cpp: do not index the section
    collection with a stale -1 when ACE resolves a section name that IndexOf()
    did not find.  ACE compares section names case-insensitively and IndexOf()
    does not, so a case-different section name in webconfig.ini threw
    MgIndexOutOfRangeException out of MgConfiguration::GetValue().

  * Web/src/WebSupport/InitializeWebTier.cpp: discard the configuration instance
    when the load throws, so a retry reloads instead of reusing a half-open heap;
    discard it from MgUninitializeWebTier() so a re-initialisation reloads
    instead of reusing a heap built on ACE state that ACE::fini() has torn down;
    and restore the throws for the coordinate system initialisation failures,
    which had been reduced to a bare `new MgException(...)` (leaked, not thrown)
    and, for a NULL catalog, let execution carry on to dereference that NULL.

  * Web/src/IsapiAgent/IsapiAgent.cpp: APPL_PHYSICAL_PATH was read into an
    uninitialised buffer with the GetServerVariable() result ignored, so a
    missing variable produced a garbage configuration path and a strlen() over
    uninitialised stack.  Zero and check the buffer, and fall back to the
    extension's own directory - the mapagent application directory the variable
    would have named.

  * Web/src/HttpHandler/*: restore two more throws of the same flavour.  An
    unknown OPERATION reached a NULL handler factory function pointer, and
    disabling WFS/WMS/OGC via AgentProperties was ignored.

Verified on Windows x64 Release (WebTier.sln, v142 toolset): a first
MgInitializeWebTier() with a missing configuration now throws
MgConfigurationLoadFailedException with the tier left uninitialised, and
subsequent calls with a valid configuration load it and initialise successfully.
Previously they returned normally with no configuration loaded, or failed with
MgFileNotFoundException on Resources/mapguide_en.res.  LogRequest's
AgentProperties lookup succeeds afterwards, as does a re-initialisation after
MgUninitializeWebTier().

Fixes #2224
Fixes #2196
Fixes #2401

Modified: branches/4.0/MgDev/Common/Foundation/System/Configuration.cpp
===================================================================
--- branches/4.0/MgDev/Common/Foundation/System/Configuration.cpp	2026-09-22 11:16:06 UTC (rev 10218)
+++ branches/4.0/MgDev/Common/Foundation/System/Configuration.cpp	2026-10-19 16:06:14 UTC (rev 10219)
@@ -71,6 +71,23 @@
     return MgConfiguration::sm_configuration;
 }
 
+/// <summary>
+/// Discards the process-wide MgConfiguration, if one exists.
+/// </summary>
+void MgConfiguration::DiscardInstance()
+{
+    // Serialize with GetInstance(). The lock is recursive, so this is safe to call from
+    // within MgInitializeWebTier(), which holds the same lock.
+    ACE_MT (ACE_GUARD (ACE_Recursive_Thread_Mutex, ace_mon, *ACE_Static_Object_Lock::instance ()));
+
+    MG_CONFIGURATION_TRY()
+
+    // Do not assign the instance to a Ptr anywhere else; this is the only owner.
+    MgConfiguration::sm_configuration = (MgConfiguration*)NULL;
+
+    MG_CONFIGURATION_CATCH_AND_THROW(L"MgConfiguration.DiscardInstance")
+}
+
 ///----------------------------------------------------------------------------
 /// <summary>
 /// Loads the configuration file specified
@@ -97,25 +114,22 @@
     {
         m_fileName = fileName;
 
-        if(m_config.Open())
+        if (m_config.Open() && m_config.ImportConfig(fileName))
         {
-            if(m_config.ImportConfig(fileName))
-            {
-                m_fileLoaded = true;
-            }
-            else
-            {
-                MgStringCollection arguments;
-                arguments.Add(fileName);
-
-                throw new MgException(MgExceptionCodes::MgConfigurationLoadFailedException, 
-                    L"MgConfiguration.LoadConfiguration",
-                    __LINE__, __WFILE__, &arguments, L"", NULL);
-            }
+            m_fileLoaded = true;
         }
         else
         {
-            ACE_ERROR((LM_ERROR, ACE_TEXT("%p\n"), ACE_TEXT("MgConfiguration::LoadConfiguration()")));
+            // Report a failure to open the heap as well as a failure to import the file.
+            // The heap stays open after either failure and cannot be re-opened, so this
+            // instance can never be loaded; callers must discard it (see DiscardInstance)
+            // rather than treat this as a successful load.
+            MgStringCollection arguments;
+            arguments.Add(fileName);
+
+            throw new MgException(MgExceptionCodes::MgConfigurationLoadFailedException, 
+                L"MgConfiguration.LoadConfiguration",
+                __LINE__, __WFILE__, &arguments, L"", NULL);
         }
     }
 

Modified: branches/4.0/MgDev/Common/Foundation/System/Configuration.h
===================================================================
--- branches/4.0/MgDev/Common/Foundation/System/Configuration.h	2026-09-22 11:16:06 UTC (rev 10218)
+++ branches/4.0/MgDev/Common/Foundation/System/Configuration.h	2026-10-19 16:06:14 UTC (rev 10219)
@@ -83,6 +83,19 @@
     static MgConfiguration* GetInstance();
 
     /// \brief
+    /// Discards the process-wide instance, if one exists.
+    ///
+    /// The underlying ACE configuration heap can only be opened once per instance,
+    /// so an instance whose LoadConfiguration() failed can never load a file. Callers
+    /// that want to retry a failed load must discard the instance first, so that the
+    /// next GetInstance() starts from a clean heap.
+    ///
+    /// Do not call this while the instance is in use by another thread.
+    ///
+
+    static void DiscardInstance();
+
+    /// \brief
     /// Loads the configuration file specified
     ///
     /// \param fileName

Modified: branches/4.0/MgDev/Common/Foundation/System/ConfigurationHeap.cpp
===================================================================
--- branches/4.0/MgDev/Common/Foundation/System/ConfigurationHeap.cpp	2026-09-22 11:16:06 UTC (rev 10218)
+++ branches/4.0/MgDev/Common/Foundation/System/ConfigurationHeap.cpp	2026-10-19 16:06:14 UTC (rev 10219)
@@ -332,6 +332,15 @@
         {
             return false;
         }
+
+        if (index < 0)
+        {
+            // ACE compares section names case-insensitively, whereas IndexOf() does not,
+            // so ACE can resolve a section this collection has no entry for. Do not index
+            // the collection with the stale -1; report the section as not found instead.
+            return false;
+        }
+
         section = m_sectionCollection->GetItem(index);
     }
 

Modified: branches/4.0/MgDev/Web/src/HttpHandler/HttpRequest.cpp
===================================================================
--- branches/4.0/MgDev/Web/src/HttpHandler/HttpRequest.cpp	2026-09-22 11:16:06 UTC (rev 10218)
+++ branches/4.0/MgDev/Web/src/HttpHandler/HttpRequest.cpp	2026-10-19 16:06:14 UTC (rev 10219)
@@ -325,7 +325,7 @@
     //find in the map the creator for the http mapping class
     MgHttpRequestResponseHandler* (*createFunc)(MgHttpRequest*) = httpClassCreators[OperationName];
     if(createFunc == NULL)
-        new MgException(MgExceptionCodes::MgInvalidOperationException, L"MgHttpRequest::CreateRequestResponseHandler", __LINE__, __WFILE__, NULL, L"", NULL);
+        throw new MgException(MgExceptionCodes::MgInvalidOperationException, L"MgHttpRequest::CreateRequestResponseHandler", __LINE__, __WFILE__, NULL, L"", NULL);
 
     //instantiate the http class
     returnHandler = (*createFunc)(this);

Modified: branches/4.0/MgDev/Web/src/HttpHandler/HttpRequestResponseHandler.cpp
===================================================================
--- branches/4.0/MgDev/Web/src/HttpHandler/HttpRequestResponseHandler.cpp	2026-09-22 11:16:06 UTC (rev 10218)
+++ branches/4.0/MgDev/Web/src/HttpHandler/HttpRequestResponseHandler.cpp	2026-10-19 16:06:14 UTC (rev 10219)
@@ -69,7 +69,7 @@
         config->GetBoolValue(MgConfigProperties::AgentPropertiesSection, disableProperty, bDisable, false);
         if (bDisable)
         {
-            new MgException(MgExceptionCodes::MgInvalidOperationException, L"HttpRequestResponseHandler.InitializeCommonParamters", __LINE__, __WFILE__, NULL, L"", NULL);
+            throw new MgException(MgExceptionCodes::MgInvalidOperationException, L"HttpRequestResponseHandler.InitializeCommonParamters", __LINE__, __WFILE__, NULL, L"", NULL);
         }
     }
 

Modified: branches/4.0/MgDev/Web/src/IsapiAgent/IsapiAgent.cpp
===================================================================
--- branches/4.0/MgDev/Web/src/IsapiAgent/IsapiAgent.cpp	2026-09-22 11:16:06 UTC (rev 10218)
+++ branches/4.0/MgDev/Web/src/IsapiAgent/IsapiAgent.cpp	2026-10-19 16:06:14 UTC (rev 10219)
@@ -21,6 +21,7 @@
 #include "stdafx.h"
 #include "httpext.h"
 #include "stdio.h"
+#include <stdlib.h>
 #include "WebSupport.h"
 #include "IsapiPostParser.h"
 #include "Base64.h"
@@ -31,10 +32,17 @@
 
 
 void Initialize(EXTENSION_CONTROL_BLOCK *pECB);
+static STRING GetModuleDirectory();
 STRING gConfigPath;
+static HMODULE g_hModule = NULL;
 
 BOOL APIENTRY DllMain(HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
 {
+    if (DLL_PROCESS_ATTACH == ul_reason_for_call)
+    {
+        g_hModule = (HMODULE)hModule;
+    }
+
     return TRUE;
 }
 
@@ -231,14 +239,23 @@
 {
     if (!IsWebTierInitialized())
     {
+        // GetServerVariable leaves the buffer untouched when the variable is not found,
+        // so it must start out empty and the return value must be checked.
         DWORD dwSize = 1024;
-        char path[1024];
-        pECB->GetServerVariable(pECB->ConnID, (LPSTR)MapAgentStrings::PhysicalPath, path, &dwSize);
+        char path[1024] = {0};
+        BOOL bPhysicalPath = pECB->GetServerVariable(pECB->ConnID, (LPSTR)MapAgentStrings::PhysicalPath, path, &dwSize);
 
-        if (strlen(path) > 0)
+        if (bPhysicalPath && strlen(path) > 0)
         {
             gConfigPath = MgUtil::MultiByteToWideChar(string(path));
         }
+        else
+        {
+            // APPL_PHYSICAL_PATH is not always supplied (IIS 6 does not honour it). The
+            // extension is installed in the mapagent application directory, which is what
+            // the variable would have held, so use this module's own directory instead.
+            gConfigPath = GetModuleDirectory();
+        }
 
         STRING configFile = gConfigPath;
         configFile.append(L"\\");
@@ -251,3 +268,31 @@
         MG_CATCH_AND_THROW(L"IsapiAgent.Initialize");
     }
 }
+
+
+// Returns the directory containing this extension, without a trailing separator.
+static STRING GetModuleDirectory()
+{
+    wchar_t modulePath[_MAX_PATH + 1];
+    DWORD length = 0;
+
+    if (NULL != g_hModule)
+    {
+        length = GetModuleFileNameW(g_hModule, modulePath, _MAX_PATH);
+    }
+
+    if (length == 0 || length >= _MAX_PATH)
+    {
+        return L"";
+    }
+
+    wchar_t* lastSeparator = wcsrchr(modulePath, L'\\');
+    if (NULL == lastSeparator)
+    {
+        return L"";
+    }
+
+    *lastSeparator = L'\0';
+
+    return modulePath;
+}

Modified: branches/4.0/MgDev/Web/src/WebSupport/InitializeWebTier.cpp
===================================================================
--- branches/4.0/MgDev/Web/src/WebSupport/InitializeWebTier.cpp	2026-09-22 11:16:06 UTC (rev 10218)
+++ branches/4.0/MgDev/Web/src/WebSupport/InitializeWebTier.cpp	2026-10-19 16:06:14 UTC (rev 10219)
@@ -44,7 +44,19 @@
     // Load configuration file
     MgConfiguration* config = MgConfiguration::GetInstance();
 
-    config->LoadConfiguration(configFile);
+    try
+    {
+        config->LoadConfiguration(configFile);
+    }
+    catch (...)
+    {
+        // A failed load leaves the ACE configuration heap open, and that heap cannot be
+        // re-opened, so the instance can never be loaded. Discard it so that the next
+        // request retries the initialization instead of running against a configuration
+        // that was never loaded.
+        MgConfiguration::DiscardInstance();
+        throw;
+    }
 
     // Init the Anonymous user check
     MgHttpAnonymousCheck* anonCheck = MgHttpAnonymousCheck::GetInstance();
@@ -87,7 +99,7 @@
     //Check catalog
     if (NULL == csCatalog.p)
     {
-        new MgException(MgExceptionCodes::MgCoordinateSystemInitializationFailedException, 
+        throw new MgException(MgExceptionCodes::MgCoordinateSystemInitializationFailedException, 
             L"MgInitializeWebTierInternal", __LINE__, __WFILE__, NULL, L"", NULL);
     }
 
@@ -107,7 +119,7 @@
     LibraryStatus status = csCatalog->GetLibraryStatus();
     if(lsInitialized != status)
     {
-        new MgException(MgExceptionCodes::MgCoordinateSystemInitializationFailedException, 
+        throw new MgException(MgExceptionCodes::MgCoordinateSystemInitializationFailedException, 
             L"MgInitializeWebTierInternal", __LINE__, __WFILE__, NULL, L"", NULL);
     }
 
@@ -137,6 +149,11 @@
         ACE_OS::sleep(2);
     }
 
+    // The configuration heap cannot be re-opened once opened, and it is built on ACE state
+    // that is about to be torn down, so drop it. A later MgInitializeWebTier() then loads
+    // the configuration again, exactly as it does in a fresh process.
+    MgConfiguration::DiscardInstance();
+
     // Uninitialize ACE
     ACE::fini();
 



More information about the mapguide-commits mailing list