blob: 28255c54a48e643f39faf1bffb524efe20acdc1d [file] [log] [blame]
/*
* Copyright 2000-2012 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.application.ApplicationManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.util.ThrowableConsumer;
import com.intellij.util.ThrowableConvertor;
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 org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.io.SVNRepository;
import java.util.*;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 7/30/12
* Time: 3:39 PM
*/
public class CachingSvnRepositoryPool implements SvnRepositoryPool {
private static final long DEFAULT_IDLE_TIMEOUT = 60*1000;
private static final int ourMaxCachedDefault = 5;
private static final int ourMaxConcurrentDefault = 20;
static final int ourMaxTotal = 100;
private final int myMaxCached; // per host
private int myMaxConcurrent; // per host
private final ThrowableConvertor<SVNURL, SVNRepository, SVNException> myCreator;
private final ThrowableConsumer<Pair<SVNURL, SVNRepository>, SVNException> myAdjuster;
private final Map<String, RepoGroup> myGroups;
private ApplicationLevelNumberConnectionsGuard myGuard;
private long myConnectionTimeout;
private boolean myDisposed;
private final Object myLock;
public CachingSvnRepositoryPool(ThrowableConvertor<SVNURL, SVNRepository, SVNException> creator,
final int maxCached, final int maxConcurrent,
ThrowableConsumer<Pair<SVNURL, SVNRepository>, SVNException> adjuster,
final ApplicationLevelNumberConnectionsGuard guard) {
myGuard = guard;
myLock = new Object();
myConnectionTimeout = DEFAULT_IDLE_TIMEOUT;
myCreator = creator;
myAdjuster = adjuster;
myMaxCached = maxCached > 0 ? maxCached : ourMaxCachedDefault;
myMaxConcurrent = maxConcurrent > 0 ? maxConcurrent : ourMaxConcurrentDefault;
if (myMaxConcurrent < myMaxCached) {
myMaxConcurrent = myMaxCached;
}
myGroups = new HashMap<String, RepoGroup>();
myDisposed = false;
}
public CachingSvnRepositoryPool(ThrowableConvertor<SVNURL, SVNRepository, SVNException> creator,
ThrowableConsumer<Pair<SVNURL, SVNRepository>, SVNException> adjuster,
final ApplicationLevelNumberConnectionsGuard guard) {
this(creator, -1, -1, adjuster, guard);
}
public void setConnectionTimeout(long connectionTimeout) {
synchronized (myLock) {
myConnectionTimeout = connectionTimeout;
}
}
public void waitingInterrupted() {
synchronized (myLock) {
for (RepoGroup group : myGroups.values()) {
group.interruptWaiting();
}
}
}
public void check() {
synchronized (myLock) {
if (myDisposed) return;
for (RepoGroup group : myGroups.values()) {
group.recheck();
}
}
}
@Override
public SVNRepository getRepo(SVNURL url, boolean mayReuse) throws SVNException {
synchronized (myLock) {
if (myDisposed) throw new ProcessCanceledException();
final String host = url.getHost();
RepoGroup group = myGroups.get(host);
if (group == null) {
group = new RepoGroup(myCreator, myMaxCached, myMaxConcurrent, myAdjuster, myGuard, myLock, myConnectionTimeout);
}
myGroups.put(host, group);
return group.getRepo(url, mayReuse);
}
}
@Override
public void returnRepo(SVNRepository repo) {
synchronized (myLock) {
if (myDisposed) {
repo.closeSession();
myGuard.connectionDestroyed(1);
return;
}
final String host = repo.getLocation().getHost();
RepoGroup group = myGroups.get(host);
assert group != null;
group.returnRepo(repo);
}
}
@Override
public void dispose() {
synchronized (myLock) {
myDisposed = true;
for (RepoGroup group : myGroups.values()) {
group.dispose();
}
}
}
public void closeInactive() {
synchronized (myLock) {
for (RepoGroup group : myGroups.values()) {
group.closeInactive();
}
}
}
// per host
public static class RepoGroup implements SvnRepositoryPool {
private final ThrowableConvertor<SVNURL, SVNRepository, SVNException> myCreator;
private final int myMaxCached; // per host
private final int myMaxConcurrent; // per host
private final ThrowableConsumer<Pair<SVNURL, SVNRepository>, SVNException> myAdjuster;
private final ApplicationLevelNumberConnectionsGuard myGuard;
private final TreeMap<Long, SVNRepository> myInactive;
private final Set<SVNRepository> myUsed;
private boolean myDisposed;
private final Object myWait;
private long myConnectionTimeout;
private RepoGroup(ThrowableConvertor<SVNURL, SVNRepository, SVNException> creator, int cached, int concurrent,
final ThrowableConsumer<Pair<SVNURL, SVNRepository>, SVNException> adjuster,
final ApplicationLevelNumberConnectionsGuard guard, final Object waitObj, final long connectionTimeout) {
myCreator = creator;
myMaxCached = cached;
myMaxConcurrent = concurrent;
myAdjuster = adjuster;
myGuard = guard;
myConnectionTimeout = connectionTimeout;
myInactive = new TreeMap<Long, SVNRepository>();
myUsed = new HashSet<SVNRepository>();
myDisposed = false;
myWait = waitObj;
}
public void dispose() {
final List<SVNRepository> listForClose = new ArrayList<SVNRepository>();
listForClose.addAll(myInactive.values());
myInactive.clear();
myUsed.clear(); // todo use counter instead of list??
synchronized (myWait) {
myWait.notifyAll();
}
myDisposed = true;
myWait.notifyAll();
myGuard.connectionDestroyed(listForClose.size());
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
for (SVNRepository repository : listForClose) {
repository.closeSession();
}
}
});
}
public void interruptWaiting() {
synchronized (myWait) {
myWait.notifyAll();
}
}
@Override
public SVNRepository getRepo(SVNURL url, boolean mayReuse) throws SVNException {
if (myDisposed) return null;
if (! myInactive.isEmpty() && mayReuse) {
return fromInactive(url);
}
myGuard.waitForTotalNumberOfConnectionsOk();
if (myUsed.size() >= myMaxConcurrent) {
synchronized (myWait) {
if ((myUsed.size() + myInactive.size()) >= myMaxConcurrent) {
while ((myUsed.size() + myInactive.size()) >= myMaxConcurrent && ! myDisposed) {
try {
myWait.wait(300);
}
catch (InterruptedException e) {
//
}
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null && indicator.isCanceled()) {
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.CANCELLED));
}
}
//somewhy unblocked
if (myDisposed) {
myWait.notifyAll(); // unblock others
throw new ProcessCanceledException();
}
}
}
}
if (! myInactive.isEmpty() && mayReuse) {
return fromInactive(url);
}
assert myUsed.size() <= myMaxConcurrent;
final SVNRepository fun = myCreator.convert(url);
myUsed.add(fun);
myGuard.connectionCreated();
return fun;
}
private SVNRepository fromInactive(SVNURL url) throws SVNException {
// oldest
final Map.Entry<Long, SVNRepository> entry = myInactive.firstEntry();
final SVNRepository next = entry.getValue();
myInactive.remove(entry.getKey());
myAdjuster.consume(Pair.create(url, next));
if (! myGuard.shouldKeepConnectionLocally()) {
myInactive.clear();
}
return next;
}
@Override
public void returnRepo(SVNRepository repo) {
myUsed.remove(repo);
if (myGuard.shouldKeepConnectionLocally() && myInactive.size() < myMaxCached) {
long time = System.currentTimeMillis();
if (myInactive.containsKey(time)) {
time = myInactive.lastKey() + 1;
}
myInactive.put(time, repo);
} else {
repo.closeSession();
myGuard.connectionDestroyed(1);
}
}
public void recheck() {
final long time = System.currentTimeMillis();
final Set<Long> longs = myInactive.keySet();
final Iterator<Long> iterator = longs.iterator();
while (iterator.hasNext()) {
final Long next = iterator.next();
if (time - next > myConnectionTimeout) {
myInactive.get(next).closeSession();
myGuard.connectionDestroyed(1);
iterator.remove();
} else {
break;
}
}
}
public int closeInactive() {
int cnt = myInactive.size();
for (SVNRepository repository : myInactive.values()) {
repository.closeSession();
myGuard.connectionDestroyed(1);
}
myInactive.clear();
return cnt;
}
public int getUsedSize() {
return myUsed.size();
}
public int getInactiveSize() {
return myInactive.size();
}
}
public Map<String, RepoGroup> getGroups() {
assert ApplicationManager.getApplication().isUnitTestMode();
return myGroups;
}
}