-
Notifications
You must be signed in to change notification settings - Fork 602
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added keep-alive mechanism that detects disconnects (Fixes #166)
- Loading branch information
1 parent
a7872b3
commit a7802dd
Showing
16 changed files
with
318 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/** | ||
* Copyright 2009 sshj contributors | ||
* | ||
* 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 net.schmizz.keepalive; | ||
|
||
import net.schmizz.sshj.common.Message; | ||
import net.schmizz.sshj.common.SSHPacket; | ||
import net.schmizz.sshj.connection.ConnectionImpl; | ||
import net.schmizz.sshj.transport.TransportException; | ||
|
||
final class Heartbeater | ||
extends KeepAlive { | ||
|
||
Heartbeater(ConnectionImpl conn) { | ||
super(conn, "heartbeater"); | ||
} | ||
|
||
@Override | ||
protected void doKeepAlive() throws TransportException { | ||
conn.getTransport().write(new SSHPacket(Message.IGNORE)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package net.schmizz.keepalive; | ||
|
||
import net.schmizz.sshj.common.Message; | ||
import net.schmizz.sshj.common.SSHPacket; | ||
import net.schmizz.sshj.connection.ConnectionException; | ||
import net.schmizz.sshj.connection.ConnectionImpl; | ||
import net.schmizz.sshj.transport.TransportException; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public abstract class KeepAlive extends Thread { | ||
protected final Logger log = LoggerFactory.getLogger(getClass()); | ||
|
||
protected final ConnectionImpl conn; | ||
|
||
protected int keepAliveInterval = 0; | ||
|
||
protected KeepAlive(ConnectionImpl conn, String name) { | ||
this.conn = conn; | ||
setName(name); | ||
} | ||
|
||
public synchronized int getKeepAliveInterval() { | ||
return keepAliveInterval; | ||
} | ||
|
||
public synchronized void setKeepAliveInterval(int keepAliveInterval) { | ||
this.keepAliveInterval = keepAliveInterval; | ||
if (keepAliveInterval > 0 && getState() == State.NEW) { | ||
start(); | ||
} | ||
notify(); | ||
} | ||
|
||
synchronized protected int getPositiveInterval() | ||
throws InterruptedException { | ||
while (keepAliveInterval <= 0) { | ||
wait(); | ||
} | ||
return keepAliveInterval; | ||
} | ||
|
||
@Override | ||
public void run() { | ||
log.debug("Starting {}, sending keep-alive every {} seconds", getClass().getSimpleName(), keepAliveInterval); | ||
try { | ||
while (!isInterrupted()) { | ||
final int hi = getPositiveInterval(); | ||
if (conn.getTransport().isRunning()) { | ||
log.debug("Sending keep-alive since {} seconds elapsed", hi); | ||
doKeepAlive(); | ||
} | ||
Thread.sleep(hi * 1000); | ||
} | ||
} catch (Exception e) { | ||
// If we weren't interrupted, kill the transport, then this exception was unexpected. | ||
// Else we're in shutdown-mode already, so don't forcibly kill the transport. | ||
if (!isInterrupted()) { | ||
conn.getTransport().die(e); | ||
} | ||
} | ||
|
||
log.debug("Stopping {}", getClass().getSimpleName()); | ||
|
||
} | ||
|
||
protected abstract void doKeepAlive() throws TransportException, ConnectionException; | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/net/schmizz/keepalive/KeepAliveProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package net.schmizz.keepalive; | ||
|
||
import net.schmizz.sshj.connection.ConnectionImpl; | ||
|
||
public abstract class KeepAliveProvider { | ||
|
||
public static final KeepAliveProvider HEARTBEAT = new KeepAliveProvider() { | ||
@Override | ||
public KeepAlive provide(ConnectionImpl connection) { | ||
return new Heartbeater(connection); | ||
} | ||
}; | ||
|
||
public static final KeepAliveProvider KEEP_ALIVE = new KeepAliveProvider() { | ||
@Override | ||
public KeepAlive provide(ConnectionImpl connection) { | ||
return new KeepAliveRunner(connection); | ||
} | ||
}; | ||
|
||
public abstract KeepAlive provide(ConnectionImpl connection); | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package net.schmizz.keepalive; | ||
|
||
import net.schmizz.concurrent.Promise; | ||
import net.schmizz.sshj.common.SSHPacket; | ||
import net.schmizz.sshj.connection.ConnectionException; | ||
import net.schmizz.sshj.connection.ConnectionImpl; | ||
import net.schmizz.sshj.transport.TransportException; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.util.LinkedList; | ||
import java.util.Queue; | ||
|
||
import static java.lang.String.format; | ||
import static net.schmizz.sshj.common.DisconnectReason.CONNECTION_LOST; | ||
|
||
public class KeepAliveRunner extends KeepAlive { | ||
|
||
/** The max number of keep-alives that should be unanswered before killing the connection. */ | ||
private int maxAliveCount = 5; | ||
|
||
/** The queue of promises. */ | ||
private final Queue<Promise<SSHPacket, ConnectionException>> queue = | ||
new LinkedList<Promise<SSHPacket, ConnectionException>>(); | ||
|
||
KeepAliveRunner(ConnectionImpl conn) { | ||
super(conn, "keep-alive"); | ||
} | ||
|
||
synchronized public int getMaxAliveCount() { | ||
return maxAliveCount; | ||
} | ||
|
||
synchronized public void setMaxAliveCount(int maxAliveCount) { | ||
this.maxAliveCount = maxAliveCount; | ||
} | ||
|
||
@Override | ||
protected void doKeepAlive() throws TransportException, ConnectionException { | ||
emptyQueue(queue); | ||
checkMaxReached(queue); | ||
queue.add(conn.sendGlobalRequest("keepalive@openssh.com", true, new byte[0])); | ||
} | ||
|
||
private void checkMaxReached(Queue<Promise<SSHPacket, ConnectionException>> queue) throws ConnectionException { | ||
if (queue.size() >= maxAliveCount) { | ||
throw new ConnectionException(CONNECTION_LOST, | ||
format("Did not receive any keep-alive response for %s seconds", maxAliveCount * keepAliveInterval)); | ||
} | ||
} | ||
|
||
private void emptyQueue(Queue<Promise<SSHPacket, ConnectionException>> queue) { | ||
Promise<SSHPacket, ConnectionException> peek = queue.peek(); | ||
while (peek != null && peek.isFulfilled()) { | ||
log.debug("Received response from server to our keep-alive."); | ||
queue.remove(); | ||
peek = queue.peek(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.