/* * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.idea.svn.svnkit.lowLevel; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.util.ConcurrencyUtil; import com.intellij.util.containers.hash.HashSet; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class ApplicationLevelNumberConnectionsGuardImpl implements Disposable, ApplicationLevelNumberConnectionsGuard { public static final int DELAY = 20000; private final Object myLock; private final Set<CachingSvnRepositoryPool> mySet; private int myCurrentlyActiveConnections; private int myCurrentlyOpenedConnections; private boolean myDisposed; private int myInstanceCount; // refreshable instances private final ScheduledExecutorService myService; private ScheduledFuture<?> myFuture; private final Runnable myRecheck; private int myDelay; private int myCurrentlyOpenedCount; public ApplicationLevelNumberConnectionsGuardImpl() { myDelay = DELAY; mySet = new HashSet<>(); myService = Executors.newSingleThreadScheduledExecutor(ConcurrencyUtil.newNamedThreadFactory("SVN connection")); myLock = new Object(); myDisposed = false; myRecheck = () -> { HashSet<CachingSvnRepositoryPool> pools = new HashSet<>(); synchronized (myLock) { pools.addAll(mySet); } for (CachingSvnRepositoryPool pool : pools) { pool.check(); } }; myCurrentlyActiveConnections = 0; myCurrentlyOpenedConnections = 0; } public void setDelay(int delay) { assert ApplicationManager.getApplication().isUnitTestMode(); myDelay = delay; myFuture.cancel(true); myFuture = myService.scheduleWithFixedDelay(myRecheck, myDelay, myDelay, TimeUnit.MILLISECONDS); } public int getCurrentlyActiveConnections() { synchronized (myLock) { assert ApplicationManager.getApplication().isUnitTestMode(); return myCurrentlyActiveConnections; } } public int getInstanceCount() { assert ApplicationManager.getApplication().isUnitTestMode(); return myInstanceCount; } public void addRepositoryPool(final CachingSvnRepositoryPool pool) { synchronized (myLock) { mySet.add(pool); ++ myInstanceCount; if (myFuture == null) { myFuture = myService.scheduleWithFixedDelay(myRecheck, myDelay, myDelay, TimeUnit.MILLISECONDS); } } } public void removeRepositoryPool(final CachingSvnRepositoryPool pool) { synchronized (myLock) { mySet.remove(pool); -- myInstanceCount; if (myInstanceCount == 0) { myFuture.cancel(true); myFuture = null; } } } @Override public void connectionCreated() { synchronized (myLock) { ++ myCurrentlyOpenedConnections; } } @Override public void connectionDestroyed(int number) { synchronized (myLock) { myCurrentlyOpenedConnections -= number; } } public void connectionOpened() { synchronized (myLock) { ++ myCurrentlyActiveConnections; } } public void connectionClosed() { synchronized (myLock) { -- myCurrentlyActiveConnections; myLock.notifyAll(); } } @Override public void waitForTotalNumberOfConnectionsOk() throws SVNException { synchronized (myLock) { if (myCurrentlyActiveConnections >= CachingSvnRepositoryPool.ourMaxTotal) { waitForFreeConnections(); } } // maybe too many opened? reduce request final Set<CachingSvnRepositoryPool> copy = new HashSet<>(); synchronized (myLock) { if (myCurrentlyOpenedConnections >= CachingSvnRepositoryPool.ourMaxTotal) { copy.addAll(mySet); } } for (CachingSvnRepositoryPool pool : copy) { pool.closeInactive(); } synchronized (myLock) { waitForFreeConnections(); } } private void waitForFreeConnections() throws SVNException { synchronized (myLock) { while (myCurrentlyActiveConnections >= CachingSvnRepositoryPool.ourMaxTotal && ! myDisposed) { try { myLock.wait(500); } catch (InterruptedException e) { // } ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null && indicator.isCanceled()) { throw new SVNException(SVNErrorMessage.create(SVNErrorCode.CANCELLED)); } } } } @Override public boolean shouldKeepConnectionLocally() { synchronized (myLock) { if (myCurrentlyActiveConnections > CachingSvnRepositoryPool.ourMaxTotal || myCurrentlyOpenedConnections > CachingSvnRepositoryPool.ourMaxTotal) return false; } return true; } @Override public void dispose() { synchronized (myLock) { myDisposed = true; myLock.notifyAll(); } } public int getCurrentlyOpenedCount() { synchronized (myLock) { return myCurrentlyOpenedCount; } } }