void () { while (!Thread.interrupted()) { long turnStartedAtNs = System.nanoTime(); try { boolean actuallyDidSomeMaintenance = false; if (!commandsQueue.isEmpty()) { //Closed storages are the easiest way to reclaim pages -- eat that low-hanging fruit first: int reclaimed = cleanClosedStoragesAndReclaimPages(/* max per turn: */ 1); statistics.closedStoragesReclaimed(reclaimed); actuallyDidSomeMaintenance = true; } //ensure queue is fresh enough -- drop out pages not good enough for reclaim anymore: //TODO RC: throttle it each ~5ms? Right now it is useless optimization, because we need // reclaimQueue.size() anyway, and it is basically the same scanning through the queue. // But as soon as we'll have non-O(N) size impl -- this code may be throttled, for sure int pagesRemainedForReclaim = 0; int pagesDroppedFromReclaimQueue = 0; for (Iterator<PageImpl> it = pagesToProbablyReclaim.pagesToProbablyReclaimQueue.iterator(); it.hasNext(); ) { PageImpl page = it.next(); if (!pagesForReclaimCollector.isGoodForReclaim(page)) { it.remove(); pagesDroppedFromReclaimQueue++; } else { pagesRemainedForReclaim++; } } int pagesDemandForecast = rateController.predictPagesDemandForNextTurn(); boolean pageDeficitIsLikely = isPageDeficitLikely(pagesRemainedForReclaim, pagesDemandForecast); if (pageDeficitIsLikely || pagesToProbablyReclaim.isOlderThen(turnStartedAtNs, MILLISECONDS.toNanos(500))) { //We collect pages for reclaim based on their recent utility -- but this utility changes // with time. Pages good-for-reclaim a second ago -- may become not-so-good for reclaim // anymore now. Hence, we need to refresh pagesToReclaim if exhausted -- but also regularly refillPagesForReclaim(pagesDemandForecast); //if <50% of pages collected to reclaim are clean -> try to eagerly flush some dirty pages: //MAYBE RC: flush 50% seems to be an overkill -- better to flush ~pagesToCollect. Since we // collect pages well in advance, i.e. way above forecasted demand in next 1ms, flushing // 50% is an overkill, since it is likely most of those pages won't be reclaimed until // next queue refill, or they become dirty again. int pagesFlushed = pagesForReclaimCollector.ensureEnoughCleanPagesToReclaim(0.5); actuallyDidSomeMaintenance = true; pagesRemainedForReclaim = pagesForReclaimCollector.totalPagesPreparedToReclaim(); pageDeficitIsLikely = isPageDeficitLikely(pagesRemainedForReclaim, pagesDemandForecast); } if (memoryManager.hasOverflow()) { boolean executed = releaseMemoryOverflowThrottler.runThrottled( turnStartedAtNs, () -> releasePagesAllocatedAboveCapacity(/*maxPagesToRelease: */ 10) ); actuallyDidSomeMaintenance |= executed; } long timeSpentNs = System.nanoTime() - turnStartedAtNs; if (actuallyDidSomeMaintenance) { statistics.cacheMaintenanceTurnDone(timeSpentNs); } else { statistics.cacheMaintenanceTurnSkipped(timeSpentNs); } //Assess allocation pressure and adjust our efforts: if (!pageDeficitIsLikely) { //allocation pressure low: could collect less and sleep more pagesForReclaimCollector.collectLessAggressively(); synchronized (housekeeperSleepLock) { //MAYBE RC: we could predict ~how soon we'll need refill -- based on current reclaimQueue size // and the load forecast -- and increase .wait() time accordingly, e.g. in [1..10]ms // This should reduce CPU consumption in case of low load. housekeeperSleepLock.wait(1); } } else if (pagesRemainedForReclaim > 0) { //allocation pressure high, but we ~catch up with it: just yield Thread.yield(); } else { //allocation pressure is so high we can't catch up with it: // 1) no time to wait // 2) must collect more pages-to-reclaim pagesForReclaimCollector.collectMoreAggressively(); } } catch (InterruptedException e) { break; } catch (Throwable t) { LOG.error("Exception in FilePageCache housekeeper thread (thread continue to run)", t); } } LOG.info("maintenance loop interrupted -> exiting"); }