Index: trunk/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp	(revision 88861)
+++ trunk/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp	(revision 88864)
@@ -52,10 +52,14 @@
 *********************************************************************************************************************************/
 /** Max GetCurrentPadding value we accept (to make sure it's safe to convert to bytes). */
-#define VBOX_WASAPI_MAX_PADDING         UINT32_C(0x007fffff)
+#define VBOX_WASAPI_MAX_PADDING                 UINT32_C(0x007fffff)
+
+/** Maximum number of cached device configs in each direction.
+ * The number 4 was picked at random.  */
+#define VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES    4
 
 #if 0
 /** @name WM_DRVHOSTAUDIOWAS_XXX - Worker thread messages.
  * @{ */
-#define WM_DRVHOSTAUDIOWAS_PURGE_CACHE  (WM_APP + 3)
+#define WM_DRVHOSTAUDIOWAS_PURGE_CACHE          (WM_APP + 3)
 /** @} */
 #endif
@@ -65,5 +69,6 @@
  * @{ */
 #define DRVHOSTAUDIOWAS_DO_PURGE_CACHE          ((uintptr_t)0x49f37300 + 1)
-#define DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH    ((uintptr_t)0x49f37300 + 2)
+#define DRVHOSTAUDIOWAS_DO_PRUNE_CACHE          ((uintptr_t)0x49f37300 + 2)
+#define DRVHOSTAUDIOWAS_DO_STREAM_DEV_SWITCH    ((uintptr_t)0x49f37300 + 3)
 /** @} */
 
@@ -114,4 +119,6 @@
     /** Init complete timestamp (just for reference). */
     uint64_t                    nsInited;
+    /** When it was last used. */
+    uint64_t                    nsLastUsed;
     /** The stringified properties. */
     char                        szProps[32];
@@ -258,4 +265,10 @@
      *  can do cleanups. */
     RTSEMEVENTMULTI                 hEvtCachePurge;
+    /** Total number of device config entire for capturing.
+     * This includes in-use ones. */
+    uint32_t volatile               cCacheEntriesIn;
+    /** Total number of device config entire for playback.
+     * This includes in-use ones. */
+    uint32_t volatile               cCacheEntriesOut;
 
 #if 0
@@ -720,6 +733,11 @@
  * @param   pDevCfg     Device config entry.  Must not be in the list.
  */
-static void drvHostAudioWasCacheDestroyDevConfig(PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
-{
+static void drvHostAudioWasCacheDestroyDevConfig(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEVCFG pDevCfg)
+{
+    if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN)
+        ASMAtomicDecU32(&pThis->cCacheEntriesIn);
+    else
+        ASMAtomicDecU32(&pThis->cCacheEntriesOut);
+
     uint32_t cTypeClientRefs = 0;
     if (pDevCfg->pIAudioCaptureClient)
@@ -756,5 +774,5 @@
  * @param   pDevEntry   The device entry. Must not be in the cache!
  */
-static void drvHostAudioWasCacheDestroyDevEntry(PDRVHOSTAUDIOWASCACHEDEV pDevEntry)
+static void drvHostAudioWasCacheDestroyDevEntry(PDRVHOSTAUDIOWAS pThis, PDRVHOSTAUDIOWASCACHEDEV pDevEntry)
 {
     Log8Func(("Destroying cache entry: %p - '%ls'\n", pDevEntry, pDevEntry->wszDevId));
@@ -763,5 +781,5 @@
     RTListForEachSafe(&pDevEntry->ConfigList, pDevCfg, pDevCfgNext, DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry)
     {
-        drvHostAudioWasCacheDestroyDevConfig(pDevCfg);
+        drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
     }
 
@@ -777,4 +795,52 @@
     RTMemFree(pDevEntry);
     Log8Func(("Destroyed cache entry: %p cDevRefs=%u\n", pDevEntry, cDevRefs));
+}
+
+
+/**
+ * Prunes the cache.
+ */
+static void drvHostAudioWasCachePrune(PDRVHOSTAUDIOWAS pThis)
+{
+    /*
+     * Prune each direction separately.
+     */
+    struct
+    {
+        PDMAUDIODIR        enmDir;
+        uint32_t volatile *pcEntries;
+    } aWork[] = { { PDMAUDIODIR_IN, &pThis->cCacheEntriesIn }, { PDMAUDIODIR_OUT, &pThis->cCacheEntriesOut }, };
+    for (uint32_t iWork = 0; iWork < RT_ELEMENTS(aWork); iWork++)
+    {
+        /*
+         * Remove the least recently used entry till we're below the threshold
+         * or there are no more inactive entries.
+         */
+        LogFlowFunc(("iWork=%u cEntries=%u\n", iWork, *aWork[iWork].pcEntries));
+        while (*aWork[iWork].pcEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
+        {
+            RTCritSectEnter(&pThis->CritSectCache);
+            PDRVHOSTAUDIOWASCACHEDEVCFG pLeastRecentlyUsed = NULL;
+            PDRVHOSTAUDIOWASCACHEDEV    pDevEntry;
+            RTListForEach(&pThis->CacheHead, pDevEntry, DRVHOSTAUDIOWASCACHEDEV, ListEntry)
+            {
+                if (pDevEntry->enmDir == aWork[iWork].enmDir)
+                {
+                    PDRVHOSTAUDIOWASCACHEDEVCFG pHeadCfg = RTListGetFirst(&pDevEntry->ConfigList,
+                                                                          DRVHOSTAUDIOWASCACHEDEVCFG, ListEntry);
+                    if (   pHeadCfg
+                        && (!pLeastRecentlyUsed || pHeadCfg->nsLastUsed < pLeastRecentlyUsed->nsLastUsed))
+                        pLeastRecentlyUsed = pHeadCfg;
+                }
+            }
+            if (pLeastRecentlyUsed)
+                RTListNodeRemove(&pLeastRecentlyUsed->ListEntry);
+            RTCritSectLeave(&pThis->CritSectCache);
+
+            if (!pLeastRecentlyUsed)
+                break;
+            drvHostAudioWasCacheDestroyDevConfig(pThis, pLeastRecentlyUsed);
+        }
+    }
 }
 
@@ -792,5 +858,5 @@
         if (!pDevEntry)
             break;
-        drvHostAudioWasCacheDestroyDevEntry(pDevEntry);
+        drvHostAudioWasCacheDestroyDevEntry(pThis, pDevEntry);
     }
 
@@ -820,4 +886,5 @@
         {
             RTListNodeRemove(&pDevCfg->ListEntry);
+            pDevCfg->nsLastUsed = RTTimeNanoTS();
             return pDevCfg;
         }
@@ -874,5 +941,6 @@
     {
         LogRelMax(64, ("WasAPI: Activate(%ls, IAudioClient) failed: %Rhrc\n", pDevEntry->wszDevId, hrc));
-        pDevCfg->nsInited = RTTimeNanoTS();
+        pDevCfg->nsInited   = RTTimeNanoTS();
+        pDevCfg->nsLastUsed = pDevCfg->nsInited;
         return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
     }
@@ -944,4 +1012,5 @@
                                                                                     cDefaultPeriodInNtTicks * 100);
                             pDevCfg->nsInited           = RTTimeNanoTS();
+                            pDevCfg->nsLastUsed         = pDevCfg->nsInited;
                             pDevCfg->rcSetup            = VINF_SUCCESS;
 
@@ -984,5 +1053,6 @@
     if (pClosestMatch)
         CoTaskMemFree(pClosestMatch);
-    pDevCfg->nsInited = RTTimeNanoTS();
+    pDevCfg->nsInited   = RTTimeNanoTS();
+    pDevCfg->nsLastUsed = 0;
     Log8Func(("returns VERR_AUDIO_STREAM_COULD_NOT_CREATE (inited in %'RU64 ns)\n", pDevCfg->nsInited - pDevCfg->nsCreated));
     return pDevCfg->rcSetup = VERR_AUDIO_STREAM_COULD_NOT_CREATE;
@@ -1028,4 +1098,18 @@
     PDMAudioPropsToString(&pDevCfg->Props, pDevCfg->szProps, sizeof(pDevCfg->szProps));
     pDevCfg->nsCreated         = RTTimeNanoTS();
+    pDevCfg->nsLastUsed        = pDevCfg->nsCreated;
+
+    uint32_t cCacheEntries;
+    if (pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN)
+        cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesIn);
+    else
+        cCacheEntries = ASMAtomicIncU32(&pThis->cCacheEntriesOut);
+    if (cCacheEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
+    {
+        LogFlowFunc(("Trigger cache pruning.\n"));
+        int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/,
+                                                              DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/);
+        AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis));
+    }
 
     if (!fOnWorker)
@@ -1176,10 +1260,20 @@
         RTCritSectEnter(&pThis->CritSectCache);
         RTListAppend(&pDevCfg->pDevEntry->ConfigList, &pDevCfg->ListEntry);
+        uint32_t const cEntries = pDevCfg->pDevEntry->enmDir == PDMAUDIODIR_IN ? pThis->cCacheEntriesIn : pThis->cCacheEntriesOut;
         RTCritSectLeave(&pThis->CritSectCache);
+
+        /* Trigger pruning if we're over the threshold. */
+        if (cEntries > VBOX_WASAPI_MAX_TOTAL_CONFIG_ENTRIES)
+        {
+            LogFlowFunc(("Trigger cache pruning.\n"));
+            int rc2 = pThis->pIHostAudioPort->pfnDoOnWorkerThread(pThis->pIHostAudioPort, NULL /*pStream*/,
+                                                                  DRVHOSTAUDIOWAS_DO_PRUNE_CACHE, NULL /*pvUser*/);
+            AssertRCStmt(rc2, drvHostAudioWasCachePrune(pThis));
+        }
     }
     else
     {
         Log8Func(("IAudioClient::Reset failed (%Rhrc) on %p/'%s', destroying it.\n", hrc, pDevCfg, pDevCfg->szProps));
-        drvHostAudioWasCacheDestroyDevConfig(pDevCfg);
+        drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
     }
 }
@@ -1599,5 +1693,5 @@
         LogRelMax(64, ("WasAPI: Failed to set up new device config '%ls:%s' for stream '%s': %Rrc\n",
                        pDevCfg->pDevEntry->wszDevId, pDevCfg->szProps, pStreamWas->Cfg.szName, rc));
-        drvHostAudioWasCacheDestroyDevConfig(pDevCfg);
+        drvHostAudioWasCacheDestroyDevConfig(pThis, pDevCfg);
         pThis->pIHostAudioPort->pfnStreamNotifyDeviceChanged(pThis->pIHostAudioPort, &pStreamWas->Core, true /*fReInit*/);
     }
@@ -1620,4 +1714,10 @@
             Assert(pvUser == NULL);
             drvHostAudioWasCachePurge(pThis, true /*fOnWorker*/);
+            break;
+
+        case DRVHOSTAUDIOWAS_DO_PRUNE_CACHE:
+            Assert(pStream == NULL);
+            Assert(pvUser == NULL);
+            drvHostAudioWasCachePrune(pThis);
             break;
 
