Ensure Exceptions when reporting Transaction results does not cause a stall
- When TransactionManager reports onError/onTransactionCompleted, ensure that
an Exception doesn't cause the TransactionManager to stall indefinitely.
- When processTransaction throws an Exception, ensure that a stall doesn't
occur (test only verification)
Flag: EXEMPT
Test: atest TelecomUnitTests:VoipCallTransactionTest
Fixes: 315880456
Change-Id: I2db1f79e4e5d95a57945c636df9aba4b0d841aa3
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
index 228bdde..424be98 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -72,12 +72,17 @@
String transactionName){
Log.i(TAG, String.format("transaction %s completed: with result=[%d]",
transactionName, result.getResult()));
- if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
- receiver.onResult(result);
- } else {
- receiver.onError(
- new CallException(result.getMessage(),
- result.getResult()));
+ try {
+ if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+ receiver.onResult(result);
+ } else {
+ receiver.onError(
+ new CallException(result.getMessage(),
+ result.getResult()));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, String.format("onTransactionCompleted: Notifying transaction result"
+ + " %s resulted in an Exception.", result), e);
}
finishTransaction();
}
@@ -85,8 +90,13 @@
@Override
public void onTransactionTimeout(String transactionName){
Log.i(TAG, String.format("transaction %s timeout", transactionName));
- receiver.onError(new CallException(transactionName + " timeout",
- CODE_OPERATION_TIMED_OUT));
+ try {
+ receiver.onError(new CallException(transactionName + " timeout",
+ CODE_OPERATION_TIMED_OUT));
+ } catch (Exception e) {
+ Log.e(TAG, String.format("onTransactionTimeout: Notifying transaction "
+ + " %s resulted in an Exception.", transactionName), e);
+ }
finishTransaction();
}
});
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
index 0a7e27d..b7848a2 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -56,6 +56,7 @@
public static final int SUCCESS = 0;
public static final int FAILED = 1;
public static final int TIMEOUT = 2;
+ public static final int EXCEPTION = 3;
private long mSleepTime;
private String mName;
@@ -70,6 +71,10 @@
@Override
public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ if (mType == EXCEPTION) {
+ mLog.append(mName).append(" exception;\n");
+ throw new IllegalStateException("TEST EXCEPTION");
+ }
CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
mHandler.postDelayed(() -> {
if (mType == SUCCESS) {
@@ -246,8 +251,89 @@
public void onError(CallException e) {
exceptionFuture.complete(e.getMessage());
}
- }; mTransactionManager.addTransaction(t, outcomeReceiver);
+ };
+ mTransactionManager.addTransaction(t, outcomeReceiver);
String message = exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
assertTrue(message.contains("timeout"));
}
+
+ @SmallTest
+ @Test
+ public void testTransactionException()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+ TestVoipCallTransaction.EXCEPTION);
+ VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeExceptionReceiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ }
+
+ @Override
+ public void onError(CallException e) {
+ exceptionFuture.complete(e.getMessage());
+ }
+ };
+ mTransactionManager.addTransaction(t1, outcomeExceptionReceiver);
+ // Transaction will timeout because the Exception caused the transaction to stop processing.
+ exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
+ assertTrue(mLog.toString().contains("t1 exception;\n"));
+ // Verify an exception in a processing a previous transaction does not stall the next one.
+ CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ resultFuture::complete;
+ mTransactionManager.addTransaction(t2, outcomeReceiver);
+ String expectedLog = "t1 exception;\nt2 success;\n";
+ assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
+ assertEquals(expectedLog, mLog.toString());
+ }
+
+ @SmallTest
+ @Test
+ public void testTransactionResultException()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ VoipCallTransaction t3 = new TestVoipCallTransaction("t3", 1000L,
+ TestVoipCallTransaction.SUCCESS);
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeExceptionReceiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ throw new IllegalStateException("RESULT EXCEPTION");
+ }
+
+ @Override
+ public void onError(CallException e) {
+ }
+ };
+ mTransactionManager.addTransaction(t1, outcomeExceptionReceiver);
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeException2Receiver =
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(VoipCallTransactionResult result) {
+ }
+
+ @Override
+ public void onError(CallException e) {
+ throw new IllegalStateException("RESULT EXCEPTION");
+ }
+ };
+ mTransactionManager.addTransaction(t2, outcomeException2Receiver);
+ // Verify an exception in a previous transaction result does not stall the next one.
+ CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+ OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+ resultFuture::complete;
+ mTransactionManager.addTransaction(t3, outcomeReceiver);
+ String expectedLog = "t1 success;\nt2 success;\nt3 success;\n";
+ assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+ resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
+ assertEquals(expectedLog, mLog.toString());
+ }
}