INT-3047 SFTP Multiplex Over Single Connection
Enable the use of multiple JSch channels over a single connection. JIRA: https://jira.springsource.org/browse/INT-3047 INT-3047: Polishing locks logic INT-3047 SFTP Reset Cache; Quiesce Shared Sessions Support resetCache() on the CachingSessionFactory - reset a shared JSch session so it is reestablished on next use - immediately close idle sessions - close in-use sessions as they are returned - close the channel when an SftpSession is closed and a shared JSch is being used - physically close a shared JSch session only when the last channel is closed Polishing Forgot to save the ftp.xml. Change CachedSession.epoch to created. INT-3047 Polishing - PR Comments - Also add missing docs for `preserve-timestamp` INT-3047 Polish - PR Comments Also change epoch to nanoseconds and use a simple != comparison with the epoch in which a session was created.
This commit is contained in:
committed by
Artem Bilan
parent
e128c80c19
commit
7bc4fe2d0b
@@ -45,6 +45,10 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
|
||||
private final SimplePool<Session<F>> pool;
|
||||
|
||||
private final boolean isSharedSessionCapable;
|
||||
|
||||
private volatile long sharedSessionEpoch;
|
||||
|
||||
/**
|
||||
* Create a CachingSessionFactory with an unlimited number of sessions.
|
||||
* @param sessionFactory the underlying session factory.
|
||||
@@ -80,6 +84,7 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
session.close();
|
||||
}
|
||||
});
|
||||
this.isSharedSessionCapable = sessionFactory instanceof SharedSessionCapable;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +110,7 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
*/
|
||||
@Override
|
||||
public Session<F> getSession() {
|
||||
return new CachedSession(this.pool.getItem());
|
||||
return new CachedSession(this.pool.getItem(), this.sharedSessionEpoch);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,27 +121,64 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
|
||||
this.pool.removeAllIdleItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache of sessions; also any in-use sessions will be closed when
|
||||
* returned to the cache.
|
||||
*/
|
||||
public synchronized void resetCache() {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Cache reset; idle sessions will be removed, in-use sessions will be closed when returned");
|
||||
}
|
||||
if (this.isSharedSessionCapable && ((SharedSessionCapable) this.sessionFactory).isSharedSession()) {
|
||||
((SharedSessionCapable) this.sessionFactory).resetSharedSession();
|
||||
}
|
||||
long sharedSessionEpoch = System.nanoTime();
|
||||
/*
|
||||
* Spin until we get a new value - nano precision but may be lower resolution.
|
||||
* We reset the epoch AFTER resetting the shared session so there is no possibility
|
||||
* of an "old" session being created in the new epoch. There is a slight possibility
|
||||
* that a "new" session might appear in the old epoch and thus be closed when returned to
|
||||
* the cache.
|
||||
*/
|
||||
while (sharedSessionEpoch == this.sharedSessionEpoch) {
|
||||
sharedSessionEpoch = System.nanoTime();
|
||||
}
|
||||
this.sharedSessionEpoch = sharedSessionEpoch;
|
||||
this.pool.removeAllIdleItems();
|
||||
}
|
||||
|
||||
private class CachedSession implements Session<F> {
|
||||
|
||||
private final Session<F> targetSession;
|
||||
|
||||
private boolean released;
|
||||
private volatile boolean released;
|
||||
|
||||
private CachedSession(Session<F> targetSession) {
|
||||
/**
|
||||
* The epoch in which this session was created.
|
||||
*/
|
||||
private final long sharedSessionEpoch;
|
||||
|
||||
private CachedSession(Session<F> targetSession, long sharedSessionEpoch) {
|
||||
this.targetSession = targetSession;
|
||||
this.sharedSessionEpoch = sharedSessionEpoch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
if (released) {
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Session already released.");
|
||||
logger.debug("Session " + targetSession + " already released.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Releasing Session back to the pool.");
|
||||
logger.debug("Releasing Session " + targetSession + " back to the pool.");
|
||||
}
|
||||
if (this.sharedSessionEpoch != CachingSessionFactory.this.sharedSessionEpoch) {
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("Closing session " + targetSession + " after reset.");
|
||||
}
|
||||
this.targetSession.close();
|
||||
}
|
||||
pool.releaseItem(targetSession);
|
||||
released = true;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.file.remote.session;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link SessionFactory} that implements this interface is capable of supporting a shared session.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public interface SharedSessionCapable {
|
||||
|
||||
/**
|
||||
* @return true if this factory uses a shared session.
|
||||
*/
|
||||
public abstract boolean isSharedSession();
|
||||
|
||||
/**
|
||||
* Resets the shared session so the next {@code #getSession()} will return a session
|
||||
* using a new connection.
|
||||
*/
|
||||
public abstract void resetSharedSession();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* 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.springframework.integration.file.remote.session;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public class CachingSessionFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testCacheAndReset() {
|
||||
TestSessionFactory factory = new TestSessionFactory();
|
||||
CachingSessionFactory<String> cache = new CachingSessionFactory<String>(factory);
|
||||
Session<String> sess1 = cache.getSession();
|
||||
assertEquals("session:1", TestUtils.getPropertyValue(sess1, "targetSession.id"));
|
||||
Session<String> sess2 = cache.getSession();
|
||||
assertEquals("session:2", TestUtils.getPropertyValue(sess2, "targetSession.id"));
|
||||
sess1.close();
|
||||
// session back to pool; should be open and reused.
|
||||
assertTrue(sess1.isOpen());
|
||||
sess1 = cache.getSession();
|
||||
assertEquals("session:1", TestUtils.getPropertyValue(sess1, "targetSession.id"));
|
||||
sess1.close();
|
||||
assertTrue(sess1.isOpen());
|
||||
// reset the cache; should close idle (sess1); sess2 should closed later
|
||||
cache.resetCache();
|
||||
assertFalse(sess1.isOpen());
|
||||
sess1 = cache.getSession();
|
||||
assertEquals("session:3", TestUtils.getPropertyValue(sess1, "targetSession.id"));
|
||||
sess1.close();
|
||||
assertTrue(sess1.isOpen());
|
||||
// session from previous epoch is closed on return
|
||||
sess2.close();
|
||||
assertFalse(sess2.isOpen());
|
||||
cache.resetCache();
|
||||
assertFalse(sess1.isOpen());
|
||||
}
|
||||
|
||||
private class TestSessionFactory implements SessionFactory<String> {
|
||||
|
||||
private int n;
|
||||
|
||||
@Override
|
||||
public Session<String> getSession() {
|
||||
return new TestSession("session:" + ++n);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TestSession implements Session<String> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String id;
|
||||
|
||||
private volatile boolean open = true;
|
||||
|
||||
private TestSession(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(String path) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(String path) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(String source, OutputStream outputStream) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(InputStream inputStream, String destination) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String directory) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String pathFrom, String pathTo) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return this.open;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String path) throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] listNames(String path) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readRaw(String source) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean finalizeRaw() throws IOException {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user