blob: 27e035f5ed9d3d2685b7144b4dda1b289c298b7a [file] [log] [blame]
/*
* Copyright 2000-2009 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 com.intellij.cvsSupport2.connections.ssh;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.util.Consumer;
import com.trilead.ssh2.Connection;
import com.trilead.ssh2.Session;
import org.jetbrains.annotations.Nullable;
import org.netbeans.lib.cvsclient.connection.AuthenticationException;
import org.netbeans.lib.cvsclient.connection.ConnectionSettings;
import org.netbeans.lib.cvsclient.connection.IConnection;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class SshSharedConnection {
private final static int CHECK_GRANULARITY = 10000;
private final static int EMPTY_CONNECTION_ALLOWED_FOR = 600000;
private final String myRepository;
private final ConnectionSettings myConnectionSettings;
private final Object myLock;
private final AtomicBoolean myValid;
private final ThrowableComputable<Connection, AuthenticationException> myConnectionFactory;
private final List<Cell> myQueue;
public SshSharedConnection(final String repository, final ConnectionSettings connectionSettings, final SshAuthentication authentication) {
myValid = new AtomicBoolean(true);
myRepository = repository;
myConnectionSettings = connectionSettings;
myLock = new Object();
myConnectionFactory = new ThrowableComputable<Connection, AuthenticationException>() {
public Connection compute() throws AuthenticationException {
try {
SshLogger.debug("connection factory called");
return SshConnectionUtils.openConnection(connectionSettings, authentication);
}
catch (AuthenticationException e) {
// todo +-
myValid.set(false);
throw e;
} catch (IOException e) {
// todo +-
myValid.set(false);
throw new AuthenticationException(e.getMessage(), e);
}
}
};
myQueue = new LinkedList<Cell>();
}
public boolean isValid() {
return myValid.get();
}
@Nullable
public IConnection getTicket() {
final long oldMoment = System.currentTimeMillis() - EMPTY_CONNECTION_ALLOWED_FOR;
IConnection result = null;
synchronized (myLock) {
SshLogger.debug("shared connection: queue size " + myQueue.size());
for (Iterator<Cell> iterator = myQueue.iterator(); iterator.hasNext();) {
final Cell cell = iterator.next();
if (result == null) {
result = cell.allocate();
} else {
// check whether to remove
if (cell.isEmptyAndOlderThen(oldMoment)) {
SshLogger.debug("empty and older");
cell.closeSelf();
iterator.remove();
}
}
}
if (result != null) return result;
SshLogger.debug("new cell");
final Cell newCell = new Cell(myConnectionFactory, myRepository);
myQueue.add(newCell);
return newCell.allocate();
}
}
// for externally scheduled process to call
// checks whether inner connections should be closed.. inner sessions be closed
public void controlSelf() {
final long oldMoment = System.currentTimeMillis() - EMPTY_CONNECTION_ALLOWED_FOR;
synchronized (myLock) {
SshLogger.debug("shared connection: control self: queue size " + myQueue.size() + " repo " + myRepository);
for (Iterator<Cell> iterator = myQueue.iterator(); iterator.hasNext();) {
final Cell cell = iterator.next();
if (cell.isClosed()) {
SshLogger.debug("shared connection: control self: closed, removing");
iterator.remove();
continue;
}
if (cell.isEmptyAndOlderThen(oldMoment)) {
SshLogger.debug("shared connection: control self: is empty and old, closing");
cell.closeSelf();
iterator.remove();
}
}
if (myQueue.isEmpty()) {
SshLogger.debug("shared connection: control self: unregister from socks proxy authenticator");
SocksAuthenticatorManager.getInstance().unregister(myConnectionSettings);
}
}
}
private class Cell {
private final static int SESSIONS_PER_CONNECTION = 8;
private final ConnectionLifeCycle myConnectionLifeCycle;
private final LinkedList<IConnection> mySessions;
private final Consumer<SshSessionConnection> myCloseListener;
private final String myRepository;
private long myTs;
private final ThrowableComputable<Session,AuthenticationException> mySessionProvider;
private Cell(final ThrowableComputable<Connection, AuthenticationException> factory, final String repository) {
myConnectionLifeCycle = new ConnectionLifeCycle(CHECK_GRANULARITY, factory);
myRepository = repository;
mySessions = new LinkedList<IConnection>();
myCloseListener = new Consumer<SshSessionConnection>() {
public void consume(final SshSessionConnection sshSessionConnection) {
synchronized (myLock) {
final boolean removed = mySessions.remove(sshSessionConnection);
SshLogger.debug("shared connection: session closed notification, removed: " + removed);
myTs = System.currentTimeMillis();
}
}
};
mySessionProvider = new ThrowableComputable<Session, AuthenticationException>() {
public Session compute() throws AuthenticationException {
final Connection connection;
synchronized (myLock) {
connection = myConnectionLifeCycle.getConnection();
}
SshLogger.debug("shared connection: opening session");
try {
final Session session = connection.openSession();
session.execCommand("cvs server");
return session;
}
catch (IOException e) {
throw new AuthenticationException(e.getMessage(), e);
}
}
};
}
// 1. check connection state
// 2. checks size
// should ask for state if null was returned
@Nullable
public IConnection allocate() {
synchronized (myLock) {
if (myConnectionLifeCycle.isClosed()) {
return null;
}
if (! myConnectionLifeCycle.hasDied()) {
SshLogger.debug("connection ok, active sessions: " + mySessions.size());
if (mySessions.size() >= SESSIONS_PER_CONNECTION) return null;
myTs = System.currentTimeMillis();
final SshSessionConnection wrapper = new SshSessionConnection(myRepository, myCloseListener, mySessionProvider);
mySessions.addLast(wrapper);
return wrapper;
}
// has died -> closing, close -> closed
myConnectionLifeCycle.setClosing();
}
closeSelf();
return null;
}
void closeSelf() {
final List<IConnection> copy;
synchronized (myLock) {
copy = new ArrayList<IConnection>(mySessions);
}
for (IConnection session : copy) {
try {
session.close();
}
catch (IOException e) {
//
}
}
synchronized (myLock) {
mySessions.clear();
myConnectionLifeCycle.close();
}
}
// .. and should be removed then
public boolean isClosed() {
synchronized (myLock) {
return myConnectionLifeCycle.isClosed();
}
}
public boolean isEmptyAndOlderThen(final long moment) {
synchronized (myLock) {
return mySessions.isEmpty() && (myTs < moment);
}
}
}
}