/** * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package org.geowebcache.diskquota; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.geowebcache.GeoWebCacheExtensions; import org.geowebcache.storage.StorageBroker; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import org.springframework.util.Assert; public class QuotaUpdatesMonitor { private static final Log log = LogFactory.getLog(QuotaUpdatesMonitor.class); private static final CustomizableThreadFactory tf = new CustomizableThreadFactory( "GWC DiskQuota Updates Gathering Thread-"); private final DiskQuotaConfig quotaConfig; private final StorageBroker storageBroker; private final QuotaStore quotaStore; private ExecutorService executorService; private BlockingQueue<QuotaUpdate> sharedQueue; private QueuedQuotaUpdatesProducer quotaDiffsProducer; private QueuedQuotaUpdatesConsumer quotaUsageUpdatesConsumer; public QuotaUpdatesMonitor(final DiskQuotaConfig quotaConfig, final StorageBroker storageBroker, final QuotaStore quotaStore) { Assert.notNull(quotaConfig, "quotaConfig is null"); Assert.notNull(storageBroker, "storageBroker is null"); Assert.notNull(quotaStore, "quotaStore is null"); this.quotaConfig = quotaConfig; this.storageBroker = storageBroker; this.quotaStore = quotaStore; String sizeStr = GeoWebCacheExtensions.getProperty("GEOWEBCACHE_QUOTA_QUEUE_SIZE"); int quotaQueueSize = 1000; if(sizeStr != null) { quotaQueueSize = Integer.parseInt(sizeStr); } if(quotaQueueSize > 0) { this.sharedQueue = new LinkedBlockingQueue<QuotaUpdate>(quotaQueueSize); } else { this.sharedQueue = new LinkedBlockingQueue<QuotaUpdate>(); } } public void startUp() { executorService = Executors.newSingleThreadExecutor(tf); quotaDiffsProducer = new QueuedQuotaUpdatesProducer(quotaConfig, sharedQueue, quotaStore); // the task that takes quota updates from the queue and saves them to the store quotaUsageUpdatesConsumer = new QueuedQuotaUpdatesConsumer(quotaStore, sharedQueue); // the listener that puts quota updates on the queue storageBroker.addBlobStoreListener(quotaDiffsProducer); executorService.submit(quotaUsageUpdatesConsumer); } private void shutDown(final boolean cancel) { log.info("Shutting down quota usage monitor..."); try { storageBroker.removeBlobStoreListener(quotaDiffsProducer); } catch (RuntimeException e) { log.error( "Unexpected exception while removing the disk quota monitor listener from the StorageBroker." + " Ignoring in order to continue with the monitor's shutdown " + "process", e); } if (cancel) { quotaDiffsProducer.setCancelled(true); executorService.shutdownNow(); } else { executorService.shutdown(); } sharedQueue = null; } /** * Calls for a shut down and returns immediatelly */ public void shutDownNow() { shutDown(true); } /** * Calls for a shut down and waits until any remaining task finishes before returning */ public void shutDown() { quotaUsageUpdatesConsumer.shutdown(); final boolean cancel = false; shutDown(cancel); final int maxAttempts = 6; final int seconds = 5; int attempts = 1; while (!executorService.isTerminated()) { attempts++; try { awaitTermination(seconds, TimeUnit.SECONDS); } catch (InterruptedException e) { String message = "Usage statistics thread helper for DiskQuota failed to shutdown within " + (attempts * seconds) + " seconds. Attempt " + attempts + " of " + maxAttempts + "..."; log.warn(message); if (attempts == maxAttempts) { throw new RuntimeException(message, e); } } } } public void tileStored(final String layerName, final String gridSetId, final String blobFormat, final String parametersId, final long x, final long y, final int z, final long blobSize) { this.quotaDiffsProducer.tileStored(layerName, gridSetId, blobFormat, parametersId, x, y, z, blobSize); } public void awaitTermination(int timeout, TimeUnit units) throws InterruptedException { if (!executorService.isShutdown()) { throw new IllegalStateException("Called awaitTermination but the " + "UsageStatsMonitor is not shutting down"); } executorService.awaitTermination(timeout, units); } }