1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-09-10 18:59:41 +02:00

Remove SynchronizationPoint

This continues the design started with e98d42790 ("SmackReactor/NIO,
Java8/Android19, Pretty print XML, FSM connections"), where the
exceptions that caused an operation to fail, are not recorded within
SynchronizationPoint but within the connection instance itself.
This commit is contained in:
Florian Schmaus 2020-05-26 21:39:08 +02:00
parent b1a4ccfae8
commit 57961a8cc1
15 changed files with 352 additions and 684 deletions

View file

@ -111,6 +111,7 @@ import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smack.util.Predicate;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.Supplier;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@ -174,6 +175,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
SmackConfiguration.getVersion();
}
protected enum SyncPointState {
initial,
request_sent,
successful,
}
/**
* A collection of ConnectionListeners which listen for connection closing
* and reconnection events.
@ -271,30 +278,29 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
*/
protected Writer writer;
protected final SynchronizationPoint<SmackException> tlsHandled = new SynchronizationPoint<>(this, "establishing TLS");
protected SmackException currentSmackException;
protected XMPPException currentXmppException;
protected boolean tlsHandled;
/**
* Set to success if the last features stanza from the server has been parsed. A XMPP connection
* Set to <code>true</code> if the last features stanza from the server has been parsed. A XMPP connection
* handshake can invoke multiple features stanzas, e.g. when TLS is activated a second feature
* stanza is send by the server. This is set to true once the last feature stanza has been
* parsed.
*/
protected final SynchronizationPoint<SmackException> lastFeaturesReceived = new SynchronizationPoint<>(
AbstractXMPPConnection.this, "last stream features received from server");
protected boolean lastFeaturesReceived;
/**
* Set to success if the SASL feature has been received.
* Set to <code>true</code> if the SASL feature has been received.
*/
protected final SynchronizationPoint<XMPPException> saslFeatureReceived = new SynchronizationPoint<>(
AbstractXMPPConnection.this, "SASL mechanisms stream feature from server");
protected boolean saslFeatureReceived;
/**
* A synchronization point which is successful if this connection has received the closing
* stream element from the remote end-point, i.e. the server.
*/
protected final SynchronizationPoint<Exception> closingStreamReceived = new SynchronizationPoint<>(
this, "stream closing element received");
protected boolean closingStreamReceived;
/**
* The SASLAuthentication manager that is responsible for authenticating with the server.
@ -369,8 +375,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
*/
protected boolean wasAuthenticated = false;
protected Exception currentConnectionException;
private final Map<QName, IQRequestHandler> setIqRequestHandler = new HashMap<>();
private final Map<QName, IQRequestHandler> getIqRequestHandler = new HashMap<>();
@ -486,10 +490,10 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
public abstract boolean isUsingCompression();
protected void initState() {
saslFeatureReceived.init();
lastFeaturesReceived.init();
tlsHandled.init();
// TODO: We do not init() closingStreamReceived here, as the integration tests use it to check if we waited for
currentSmackException = null;
currentXmppException = null;
saslFeatureReceived = lastFeaturesReceived = tlsHandled = false;
// TODO: We do not init closingStreamReceived here, as the integration tests use it to check if we waited for
// it.
}
@ -512,7 +516,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// Reset the connection state
initState();
closingStreamReceived.init();
closingStreamReceived = false;
streamId = null;
try {
@ -657,15 +661,82 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return streamId;
}
protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource) throws XMPPErrorException,
SmackException, InterruptedException {
protected final void throwCurrentConnectionException() throws SmackException, XMPPException {
if (currentSmackException != null) {
throw currentSmackException;
} else if (currentXmppException != null) {
throw currentXmppException;
}
throw new AssertionError("No current connection exception set, although throwCurrentException() was called");
}
protected final boolean hasCurrentConnectionException() {
return currentSmackException != null || currentXmppException != null;
}
protected final void setCurrentConnectionExceptionAndNotify(Exception exception) {
if (exception instanceof SmackException) {
currentSmackException = (SmackException) exception;
} else if (exception instanceof XMPPException) {
currentXmppException = (XMPPException) exception;
} else {
currentSmackException = new SmackException.SmackWrappedException(exception);
}
notifyWaitingThreads();
}
/**
* We use an extra object for {@link #notifyWaitingThreads()} and {@link #waitForCondition(Supplier)}, because all state
* changing methods of the connection are synchronized using the connection instance as monitor. If we now would
* also use the connection instance for the internal process to wait for a condition, the {@link Object#wait()}
* would leave the monitor when it waites, which would allow for another potential call to a state changing function
* to proceed.
*/
private final Object internalMonitor = new Object();
protected final void notifyWaitingThreads() {
synchronized (internalMonitor) {
internalMonitor.notifyAll();
}
}
protected final boolean waitForCondition(Supplier<Boolean> condition) throws InterruptedException {
final long deadline = System.currentTimeMillis() + getReplyTimeout();
synchronized (internalMonitor) {
while (!condition.get().booleanValue() && !hasCurrentConnectionException()) {
final long now = System.currentTimeMillis();
if (now >= deadline) {
return false;
}
internalMonitor.wait(deadline - now);
}
}
return true;
}
protected final void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, NoResponseException {
boolean success = waitForCondition(condition);
if (!success) {
throw NoResponseException.newWith(this, waitFor);
}
}
protected final void waitForConditionOrThrowConnectionException(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException {
waitForCondition(condition, waitFor);
if (hasCurrentConnectionException()) {
throwCurrentConnectionException();
}
}
protected Resourcepart bindResourceAndEstablishSession(Resourcepart resource)
throws SmackException, InterruptedException, XMPPException {
// Wait until either:
// - the servers last features stanza has been parsed
// - the timeout occurs
LOGGER.finer("Waiting for last features to be received before continuing with resource binding");
lastFeaturesReceived.checkIfSuccessOrWaitOrThrow();
waitForConditionOrThrowConnectionException(() -> lastFeaturesReceived, "last stream features received from server");
if (!hasFeature(Bind.ELEMENT, Bind.NAMESPACE)) {
// Server never offered resource binding, which is REQUIRED in XMPP client and
@ -905,29 +976,20 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
return;
}
// TODO: Remove this async but ordered?
ASYNC_BUT_ORDERED.performAsyncButOrdered(this, () -> {
currentConnectionException = exception;
// Note that we first have to set the current connection exception and notify waiting threads, as one of them
// could hold the instance lock, which we also need later when calling instantShutdown().
setCurrentConnectionExceptionAndNotify(exception);
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
// case the Exception is a StreamErrorException.
instantShutdown();
for (StanzaCollector collector : collectors) {
collector.notifyConnectionError(exception);
}
SmackWrappedException smackWrappedException = new SmackWrappedException(exception);
tlsHandled.reportGenericFailure(smackWrappedException);
saslFeatureReceived.reportGenericFailure(smackWrappedException);
lastFeaturesReceived.reportGenericFailure(smackWrappedException);
closingStreamReceived.reportFailure(smackWrappedException);
// TODO From XMPPTCPConnection. Was called in Smack 4.3 where notifyConnectionError() was part of
// XMPPTCPConnection. Create delegation method?
// maybeCompressFeaturesReceived.reportGenericFailure(smackWrappedException);
synchronized (AbstractXMPPConnection.this) {
notifyAll();
// Closes the connection temporary. A if the connection supports stream management, then a reconnection is
// possible. Note that a connection listener of e.g. XMPPTCPConnection will drop the SM state in
// case the Exception is a StreamErrorException.
instantShutdown();
}
Async.go(() -> {
// Notify connection listeners of the error.
@ -947,19 +1009,13 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
protected abstract void shutdown();
protected final boolean waitForClosingStreamTagFromServer() {
Exception exception;
try {
// After we send the closing stream element, check if there was already a
// closing stream element sent by the server or wait with a timeout for a
// closing stream element to be received from the server.
exception = closingStreamReceived.checkIfSuccessOrWait();
} catch (InterruptedException | NoResponseException e) {
exception = e;
waitForConditionOrThrowConnectionException(() -> closingStreamReceived, "closing stream tag from the server");
} catch (InterruptedException | SmackException | XMPPException e) {
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, e);
return false;
}
if (exception != null) {
LOGGER.log(Level.INFO, "Exception while waiting for closing stream element from the server " + this, exception);
}
return exception == null;
return true;
}
@Override
@ -1817,8 +1873,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
// Only proceed with SASL auth if TLS is disabled or if the server doesn't announce it
if (!hasFeature(StartTls.ELEMENT, StartTls.NAMESPACE)
|| config.getSecurityMode() == SecurityMode.disabled) {
tlsHandled.reportSuccess();
saslFeatureReceived.reportSuccess();
tlsHandled = saslFeatureReceived = true;
notifyWaitingThreads();
}
}
@ -1829,7 +1885,8 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
|| !config.isCompressionEnabled()) {
// This was was last features from the server is either it did not contain
// compression or if we disabled it
lastFeaturesReceived.reportSuccess();
lastFeaturesReceived = true;
notifyWaitingThreads();
}
}
afterFeaturesReceived();

View file

@ -16,6 +16,7 @@
*/
package org.jivesoftware.smack;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.List;
@ -371,15 +372,6 @@ public abstract class SmackException extends Exception {
}
}
public static class ConnectionUnexpectedTerminatedException extends SmackException {
private static final long serialVersionUID = 1L;
public ConnectionUnexpectedTerminatedException(Throwable wrappedThrowable) {
super(wrappedThrowable);
}
}
public static class FeatureNotSupportedException extends SmackException {
/**
@ -487,4 +479,19 @@ public abstract class SmackException extends Exception {
super(message, exception);
}
}
public static class SmackCertificateException extends SmackException {
private static final long serialVersionUID = 1L;
private final CertificateException certificateException;
public SmackCertificateException(CertificateException certificateException) {
this.certificateException = certificateException;
}
public CertificateException getCertificateException() {
return certificateException;
}
}
}

View file

@ -1,352 +0,0 @@
/**
*
* Copyright © 2014-2019 Florian Schmaus
*
* 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.jivesoftware.smack;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.SmackWrappedException;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
public class SynchronizationPoint<E extends Exception> {
private final AbstractXMPPConnection connection;
private final Lock connectionLock;
private final Condition condition;
private final String waitFor;
// Note that there is no need to make 'state' and 'failureException' volatile. Since 'lock' and 'unlock' have the
// same memory synchronization effects as synchronization block enter and leave.
private State state;
private E failureException;
private SmackWrappedException smackWrappedExcpetion;
private volatile long waitStart;
/**
* Construct a new synchronization point for the given connection.
*
* @param connection the connection of this synchronization point.
* @param waitFor a description of the event this synchronization point handles.
*/
public SynchronizationPoint(AbstractXMPPConnection connection, String waitFor) {
this.connection = connection;
this.connectionLock = connection.getConnectionLock();
this.condition = connection.getConnectionLock().newCondition();
this.waitFor = waitFor;
init();
}
/**
* Initialize (or reset) this synchronization point.
*/
@SuppressWarnings("LockNotBeforeTry")
public void init() {
connectionLock.lock();
state = State.Initial;
failureException = null;
smackWrappedExcpetion = null;
connectionLock.unlock();
}
/**
* Send the given top level stream element and wait for a response.
*
* @param request the plain stream element to send.
* @throws NoResponseException if no response was received.
* @throws NotConnectedException if the connection is not connected.
* @throws InterruptedException if the connection is interrupted.
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
*/
public Exception sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
NotConnectedException, InterruptedException {
assert state == State.Initial;
connectionLock.lock();
try {
if (request != null) {
if (request instanceof Stanza) {
connection.sendStanza((Stanza) request);
}
else if (request instanceof Nonza) {
connection.sendNonza((Nonza) request);
} else {
throw new IllegalStateException("Unsupported element type");
}
state = State.RequestSent;
}
waitForConditionOrTimeout();
}
finally {
connectionLock.unlock();
}
return checkForResponse();
}
/**
* Send the given plain stream element and wait for a response.
*
* @param request the plain stream element to send.
* @throws E if an failure was reported.
* @throws NoResponseException if no response was received.
* @throws NotConnectedException if the connection is not connected.
* @throws InterruptedException if the connection is interrupted.
* @throws SmackWrappedException in case of a wrapped exception;
*/
public void sendAndWaitForResponseOrThrow(Nonza request) throws E, NoResponseException,
NotConnectedException, InterruptedException, SmackWrappedException {
sendAndWaitForResponse(request);
switch (state) {
case Failure:
throwException();
break;
default:
// Success, do nothing
}
}
/**
* Check if this synchronization point is successful or wait the connections reply timeout.
* @throws NoResponseException if there was no response marking the synchronization point as success or failed.
* @throws E if there was a failure
* @throws InterruptedException if the connection is interrupted.
* @throws SmackWrappedException in case of a wrapped exception;
*/
public void checkIfSuccessOrWaitOrThrow() throws NoResponseException, E, InterruptedException, SmackWrappedException {
checkIfSuccessOrWait();
if (state == State.Failure) {
throwException();
}
}
/**
* Check if this synchronization point is successful or wait the connections reply timeout.
* @throws NoResponseException if there was no response marking the synchronization point as success or failed.
* @throws InterruptedException if the calling thread was interrupted.
* @return <code>null</code> if synchronization point was successful, or the failure Exception.
*/
public Exception checkIfSuccessOrWait() throws NoResponseException, InterruptedException {
connectionLock.lock();
try {
switch (state) {
// Return immediately on success or failure
case Success:
return null;
case Failure:
return getException();
default:
// Do nothing
break;
}
waitForConditionOrTimeout();
} finally {
connectionLock.unlock();
}
return checkForResponse();
}
/**
* Report this synchronization point as successful.
*/
public void reportSuccess() {
connectionLock.lock();
try {
state = State.Success;
condition.signalAll();
}
finally {
connectionLock.unlock();
}
}
/**
* Deprecated.
* @deprecated use {@link #reportFailure(Exception)} instead.
*/
@Deprecated
public void reportFailure() {
reportFailure(null);
}
/**
* Report this synchronization point as failed because of the given exception. The {@code failureException} must be set.
*
* @param failureException the exception causing this synchronization point to fail.
*/
public void reportFailure(E failureException) {
assert failureException != null;
connectionLock.lock();
try {
state = State.Failure;
this.failureException = failureException;
condition.signalAll();
}
finally {
connectionLock.unlock();
}
}
/**
* Report this synchronization point as failed because of the given exception. The {@code failureException} must be set.
*
* @param exception the exception causing this synchronization point to fail.
*/
public void reportGenericFailure(SmackWrappedException exception) {
assert exception != null;
connectionLock.lock();
try {
state = State.Failure;
this.smackWrappedExcpetion = exception;
condition.signalAll();
}
finally {
connectionLock.unlock();
}
}
/**
* Check if this synchronization point was successful.
*
* @return true if the synchronization point was successful, false otherwise.
*/
public boolean wasSuccessful() {
connectionLock.lock();
try {
return state == State.Success;
}
finally {
connectionLock.unlock();
}
}
public boolean isNotInInitialState() {
connectionLock.lock();
try {
return state != State.Initial;
}
finally {
connectionLock.unlock();
}
}
/**
* Check if this synchronization point has its request already sent.
*
* @return true if the request was already sent, false otherwise.
*/
public boolean requestSent() {
connectionLock.lock();
try {
return state == State.RequestSent;
}
finally {
connectionLock.unlock();
}
}
public E getFailureException() {
connectionLock.lock();
try {
return failureException;
}
finally {
connectionLock.unlock();
}
}
public void resetTimeout() {
waitStart = System.currentTimeMillis();
}
/**
* Wait for the condition to become something else as {@link State#RequestSent} or {@link State#Initial}.
* {@link #reportSuccess()}, {@link #reportFailure()} and {@link #reportFailure(Exception)} will either set this
* synchronization point to {@link State#Success} or {@link State#Failure}. If none of them is set after the
* connections reply timeout, this method will set the state of {@link State#NoResponse}.
* @throws InterruptedException if the calling thread was interrupted.
*/
private void waitForConditionOrTimeout() throws InterruptedException {
waitStart = System.currentTimeMillis();
while (state == State.RequestSent || state == State.Initial) {
long timeout = connection.getReplyTimeout();
long remainingWaitMillis = timeout - (System.currentTimeMillis() - waitStart);
long remainingWait = TimeUnit.MILLISECONDS.toNanos(remainingWaitMillis);
if (remainingWait <= 0) {
state = State.NoResponse;
break;
}
try {
condition.awaitNanos(remainingWait);
} catch (InterruptedException e) {
state = State.Interrupted;
throw e;
}
}
}
private Exception getException() {
if (failureException != null) {
return failureException;
}
return smackWrappedExcpetion;
}
private void throwException() throws E, SmackWrappedException {
if (failureException != null) {
throw failureException;
}
throw smackWrappedExcpetion;
}
/**
* Check for a response and throw a {@link NoResponseException} if there was none.
* <p>
* The exception is thrown, if state is one of 'Initial', 'NoResponse' or 'RequestSent'
* </p>
* @return <code>true</code> if synchronization point was successful, <code>false</code> on failure.
* @throws NoResponseException if there was no response from the remote entity.
*/
private Exception checkForResponse() throws NoResponseException {
switch (state) {
case Initial:
case NoResponse:
case RequestSent:
throw NoResponseException.newWith(connection, waitFor);
case Success:
return null;
case Failure:
return getException();
default:
throw new AssertionError("Unknown state " + state);
}
}
private enum State {
Initial,
RequestSent,
NoResponse,
Success,
Failure,
Interrupted,
}
}

View file

@ -67,7 +67,8 @@ public interface XmppInputOutputFilter {
default void closeInputOutput() {
}
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException, InterruptedException, SmackException {
default void waitUntilInputOutputClosed() throws IOException, NoResponseException, CertificateException,
InterruptedException, SmackException, XMPPException {
}
Object getStats();

View file

@ -35,7 +35,6 @@ import javax.net.ssl.SSLSession;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackFuture;
@ -78,6 +77,7 @@ import org.jivesoftware.smack.util.ArrayBlockingQueueWithShutdown;
import org.jivesoftware.smack.util.ExtendedAppendable;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.Supplier;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
@ -142,7 +142,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override
public void onStreamClosed() {
ModularXmppClientToServerConnection.this.closingStreamReceived.reportSuccess();
ModularXmppClientToServerConnection.this.closingStreamReceived = true;
notifyWaitingThreads();
}
@Override
@ -177,7 +178,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override
public void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
SmackException, XMPPException {
ModularXmppClientToServerConnection.this.newStreamOpenWaitForFeaturesSequence(waitFor);
}
@ -198,8 +199,14 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
@Override
public Exception getCurrentConnectionException() {
return ModularXmppClientToServerConnection.this.currentConnectionException;
public void waitForCondition(Supplier<Boolean> condition, String waitFor)
throws InterruptedException, SmackException, XMPPException {
ModularXmppClientToServerConnection.this.waitForConditionOrThrowConnectionException(condition, waitFor);
}
@Override
public void notifyWaitingThreads() {
ModularXmppClientToServerConnection.this.notifyWaitingThreads();
}
@Override
@ -263,14 +270,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
revertedState.resetState();
}
protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
SASLErrorException, FailedNonzaException, IOException, SmackException, InterruptedException {
protected void walkStateGraph(WalkStateGraphContext walkStateGraphContext)
throws XMPPException, IOException, SmackException, InterruptedException {
// Save a copy of the current state
GraphVertex<State> previousStateVertex = currentStateVertex;
try {
walkStateGraphInternal(walkStateGraphContext);
} catch (XMPPErrorException | SASLErrorException | FailedNonzaException | IOException | SmackException
| InterruptedException e) {
} catch (IOException | SmackException | InterruptedException | XMPPException e) {
currentStateVertex = previousStateVertex;
// Unwind the state.
State revertedState = currentStateVertex.getElement();
@ -279,8 +285,8 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
}
private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext) throws XMPPErrorException,
SASLErrorException, IOException, SmackException, InterruptedException, FailedNonzaException {
private void walkStateGraphInternal(WalkStateGraphContext walkStateGraphContext)
throws IOException, SmackException, InterruptedException, XMPPException {
// Save a copy of the current state
final GraphVertex<State> initialStateVertex = currentStateVertex;
final State initialState = initialStateVertex.getElement();
@ -359,15 +365,13 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
* @param walkStateGraphContext the "walk state graph" context.
* @return A state transition result or <code>null</code> if this state can be ignored.
* @throws SmackException if Smack detected an exceptional situation.
* @throws XMPPErrorException if an XMPP protocol error was received.
* @throws SASLErrorException if a SASL protocol error was returned.
* @throws XMPPException if an XMPP protocol error was received.
* @throws IOException if an I/O error occurred.
* @throws InterruptedException if the calling thread was interrupted.
* @throws FailedNonzaException if an XMPP protocol failure was received.
*/
private StateTransitionResult attemptEnterState(GraphVertex<State> successorStateVertex,
WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPErrorException,
SASLErrorException, IOException, InterruptedException, FailedNonzaException {
WalkStateGraphContext walkStateGraphContext) throws SmackException, XMPPException,
IOException, InterruptedException {
final GraphVertex<State> initialStateVertex = currentStateVertex;
final State initialState = initialStateVertex.getElement();
final State successorState = successorStateVertex.getElement();
@ -400,8 +404,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
invokeConnectionStateMachineListener(new ConnectionStateEvent.AboutToTransitionInto(initialState, successorState));
transitionAttemptResult = successorState.transitionInto(walkStateGraphContext);
} catch (SmackException | XMPPErrorException | SASLErrorException | IOException | InterruptedException
| FailedNonzaException e) {
} catch (SmackException | IOException | InterruptedException | XMPPException e) {
// Unwind the state here too, since this state will not be unwound by walkStateGraph(), as it will not
// become a predecessor state in the walk.
unwindState(successorState);
@ -474,8 +477,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
try {
walkStateGraph(context);
} catch (XMPPErrorException | SASLErrorException | IOException | SmackException | InterruptedException
| FailedNonzaException e) {
} catch (IOException | SmackException | InterruptedException | XMPPException e) {
throw new IllegalStateException("A walk to disconnected state should never throw", e);
}
}
@ -491,9 +493,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override
protected void afterFeaturesReceived() {
featuresReceived = true;
synchronized (this) {
notifyAll();
}
notifyWaitingThreads();
}
protected void parseAndProcessElement(String element) {
@ -522,8 +522,10 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
break;
case "error":
StreamError streamError = PacketParserUtils.parseStreamError(parser, null);
saslFeatureReceived.reportFailure(new StreamErrorException(streamError));
throw new StreamErrorException(streamError);
StreamErrorException streamErrorException = new StreamErrorException(streamError);
currentXmppException = streamErrorException;
notifyWaitingThreads();
throw streamErrorException;
case "features":
parseFeatures(parser);
afterFeaturesReceived();
@ -550,25 +552,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
}
protected void waitForFeaturesReceived(String waitFor)
throws InterruptedException, ConnectionUnexpectedTerminatedException, NoResponseException {
long waitStartMs = System.currentTimeMillis();
long timeoutMs = getReplyTimeout();
synchronized (this) {
while (!featuresReceived && currentConnectionException == null) {
long remainingWaitMs = timeoutMs - (System.currentTimeMillis() - waitStartMs);
if (remainingWaitMs <= 0) {
throw NoResponseException.newWith(this, waitFor);
}
wait(remainingWaitMs);
}
if (currentConnectionException != null) {
throw new SmackException.ConnectionUnexpectedTerminatedException(currentConnectionException);
}
}
throws InterruptedException, SmackException, XMPPException {
waitForConditionOrThrowConnectionException(() -> featuresReceived, waitFor);
}
protected void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException {
SmackException, XMPPException {
prepareToWaitForFeaturesReceived();
sendStreamOpen();
waitForFeaturesReceived(waitFor);
@ -763,8 +752,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException,
InterruptedException {
throws IOException, SmackException, InterruptedException, XMPPException {
prepareToWaitForFeaturesReceived();
LoginContext loginContext = walkStateGraphContext.getLoginContext();
@ -813,12 +801,12 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException,
InterruptedException {
throws IOException, SmackException, InterruptedException, XMPPException {
// Calling bindResourceAndEstablishSession() below requires the lastFeaturesReceived sync point to be signaled.
// Since we entered this state, the FSM has decided that the last features have been received, hence signal
// the sync point.
lastFeaturesReceived.reportSuccess();
lastFeaturesReceived = true;
notifyWaitingThreads();
LoginContext loginContext = walkStateGraphContext.getLoginContext();
Resourcepart resource = bindResourceAndEstablishSession(loginContext.resource);
@ -914,7 +902,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext) {
closingStreamReceived.init();
closingStreamReceived = false;
boolean streamCloseIssued = outgoingElementsQueue.offerAndShutdown(StreamClose.INSTANCE);
@ -936,7 +924,7 @@ public final class ModularXmppClientToServerConnection extends AbstractXMPPConne
XmppInputOutputFilter filter = it.next();
try {
filter.waitUntilInputOutputClosed();
} catch (IOException | CertificateException | InterruptedException | SmackException e) {
} catch (IOException | CertificateException | InterruptedException | SmackException | XMPPException e) {
LOGGER.log(Level.WARNING, "waitUntilInputOutputClosed() threw", e);
}
}

View file

@ -22,11 +22,12 @@ import java.nio.channels.SelectionKey;
import java.util.ListIterator;
import java.util.Queue;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackReactor;
import org.jivesoftware.smack.SmackReactor.ChannelSelectedCallback;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
@ -38,6 +39,7 @@ import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jivesoftware.smack.packet.XmlEnvironment;
import org.jivesoftware.smack.util.Consumer;
import org.jivesoftware.smack.util.Supplier;
import org.jivesoftware.smack.xml.XmlPullParser;
public abstract class ModularXmppClientToServerConnectionInternal {
@ -98,7 +100,7 @@ public abstract class ModularXmppClientToServerConnectionInternal {
public abstract ListIterator<XmppInputOutputFilter> getXmppInputOutputFilterEndIterator();
public abstract void newStreamOpenWaitForFeaturesSequence(String waitFor) throws InterruptedException,
ConnectionUnexpectedTerminatedException, NoResponseException, NotConnectedException;
NoResponseException, NotConnectedException, SmackException, XMPPException;
public abstract SmackTlsContext getSmackTlsContext();
@ -108,7 +110,9 @@ public abstract class ModularXmppClientToServerConnectionInternal {
public abstract void asyncGo(Runnable runnable);
public abstract Exception getCurrentConnectionException();
public abstract void waitForCondition(Supplier<Boolean> condition, String waitFor) throws InterruptedException, SmackException, XMPPException;
public abstract void notifyWaitingThreads();
public abstract void setCompressionEnabled(boolean compressionEnabled);

View file

@ -17,10 +17,8 @@
package org.jivesoftware.smack.compression;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException.ConnectionUnexpectedTerminatedException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XmppInputOutputFilter;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedButUnboundStateDescriptor;
import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.ResourceBindingStateDescriptor;
@ -90,8 +88,7 @@ public class CompressionModule extends ModularXmppClientToServerConnectionModule
@Override
public StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws NoResponseException, NotConnectedException, FailedNonzaException, InterruptedException,
ConnectionUnexpectedTerminatedException {
throws InterruptedException, SmackException, XMPPException {
final String compressionMethod = selectedCompressionFactory.getCompressionMethod();
connectionInternal.sendAndWaitForResponse(new Compress(compressionMethod), Compressed.class, Failure.class);

View file

@ -19,11 +19,9 @@ package org.jivesoftware.smack.fsm;
import java.io.IOException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;
import org.jivesoftware.smack.c2s.internal.WalkStateGraphContext;
import org.jivesoftware.smack.sasl.SASLErrorException;
/**
* Note that this is an non-static inner class of XmppClientToServerConnection so that states can inspect and modify
@ -53,8 +51,7 @@ public abstract class State {
}
public abstract StateTransitionResult.AttemptResult transitionInto(WalkStateGraphContext walkStateGraphContext)
throws XMPPErrorException, SASLErrorException, IOException, SmackException,
InterruptedException, FailedNonzaException;
throws IOException, SmackException, InterruptedException, XMPPException;
public StateDescriptor getStateDescriptor() {
return stateDescriptor;

View file

@ -67,6 +67,14 @@ public abstract class StateTransitionResult {
}
}
public static final class FailureCausedByTimeout extends Failure {
public FailureCausedByTimeout(String failureMessage) {
super(failureMessage);
}
}
public abstract static class TransitionImpossible extends StateTransitionResult {
protected TransitionImpossible(String message) {
super(message);

View file

@ -0,0 +1,24 @@
/**
*
* Copyright 2020 Florian Schmaus
*
* 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.jivesoftware.smack.util;
// TODO: Replace with java.util.function.Supplier once Smack's minimum Android SDK level is 24 or higher.
public interface Supplier<T> {
T get();
}