From f4c64ad3e20e635008e9614d4e7c65b0aeaf2b10 Mon Sep 17 00:00:00 2001 From: Janis Danisevskis Date: Fri, 22 Jan 2021 11:17:43 -0800 Subject: [PATCH] Keystore 2.0 SPI: Add back-off hint to BackendBusyException. BackendBusyException now returns a back-off hint that API users can use to implement their retry loop. Bug: 174761871 Test: N/A Change-Id: I95662a5a5432965de365017eae43c502eb5bfc06 --- core/api/current.txt | 7 ++-- .../security/KeyStoreSecurityLevel.java | 7 ++-- .../keystore/BackendBusyException.java | 37 +++++++++++++++++-- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index b0534a1415202..9f358f2df4a14 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -36183,9 +36183,10 @@ package android.security.identity { package android.security.keystore { public class BackendBusyException extends java.security.ProviderException { - ctor public BackendBusyException(); - ctor public BackendBusyException(@NonNull String); - ctor public BackendBusyException(@NonNull String, @NonNull Throwable); + ctor public BackendBusyException(long); + ctor public BackendBusyException(long, @NonNull String); + ctor public BackendBusyException(long, @NonNull String, @NonNull Throwable); + method public long getBackOffHintMillis(); } public class KeyExpiredException extends java.security.InvalidKeyException { diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java index 3ef4aa5b7ec30..56dd7f0498287 100644 --- a/keystore/java/android/security/KeyStoreSecurityLevel.java +++ b/keystore/java/android/security/KeyStoreSecurityLevel.java @@ -96,20 +96,21 @@ public class KeyStoreSecurityLevel { } catch (ServiceSpecificException e) { switch (e.errorCode) { case ResponseCode.BACKEND_BUSY: { + long backOffHint = (long) (Math.random() * 80 + 20); if (CompatChanges.isChangeEnabled( KeyStore2.KEYSTORE_OPERATION_CREATION_MAY_FAIL)) { // Starting with Android S we inform the caller about the // backend being busy. - throw new BackendBusyException(); + throw new BackendBusyException(backOffHint); } else { // Before Android S operation creation must always succeed. So we // just have to retry. We do so with a randomized back-off between - // 50 and 250ms. + // 20 and 100ms. // It is a little awkward that we cannot break out of this loop // by interrupting this thread. But that is the expected behavior. // There is some comfort in the fact that interrupting a thread // also does not unblock a thread waiting for a binder transaction. - interruptedPreservingSleep((long) (Math.random() * 200 + 50)); + interruptedPreservingSleep(backOffHint); } break; } diff --git a/keystore/java/android/security/keystore/BackendBusyException.java b/keystore/java/android/security/keystore/BackendBusyException.java index 1a88469d7e543..a813e939a7204 100644 --- a/keystore/java/android/security/keystore/BackendBusyException.java +++ b/keystore/java/android/security/keystore/BackendBusyException.java @@ -16,37 +16,66 @@ package android.security.keystore; +import android.annotation.DurationMillisLong; import android.annotation.NonNull; import java.security.ProviderException; /** * Indicates a transient error that prevented a key operation from being created. - * Callers should try again with a back-off period of 10-30 milliseconds. + * Callers should try again with a back-off period of {@link #getBackOffHintMillis()} + * milliseconds. */ public class BackendBusyException extends ProviderException { + private final long mBackOffHintMillis; + /** * Constructs a new {@code BackendBusyException} without detail message and cause. + * */ - public BackendBusyException() { + public BackendBusyException(@DurationMillisLong long backOffHintMillis) { super("The keystore backend has no operation slots available. Retry later."); + if (backOffHintMillis < 0) { + throw new IllegalArgumentException("Back-off hint cannot be negative."); + } + mBackOffHintMillis = backOffHintMillis; } /** * Constructs a new {@code BackendBusyException} with the provided detail message and * no cause. */ - public BackendBusyException(@NonNull String message) { + public BackendBusyException(@DurationMillisLong long backOffHintMillis, + @NonNull String message) { super(message); + if (backOffHintMillis < 0) { + throw new IllegalArgumentException("Back-off hint cannot be negative."); + } + mBackOffHintMillis = backOffHintMillis; } /** * Constructs a new {@code BackendBusyException} with the provided detail message and * cause. */ - public BackendBusyException(@NonNull String message, @NonNull Throwable cause) { + public BackendBusyException(@DurationMillisLong long backOffHintMillis, + @NonNull String message, @NonNull Throwable cause) { super(message, cause); + if (backOffHintMillis < 0) { + throw new IllegalArgumentException("Back-off hint cannot be negative."); + } + mBackOffHintMillis = backOffHintMillis; } + /** + * When retrying to start a Keystore operation after receiving this exception, this can be + * used to determine how long to wait before retrying. It is not guaranteed that the operation + * will succeeds after this time. Multiple retries may be necessary if the system is congested. + * + * @return Number of milliseconds to back off before retrying. + */ + public @DurationMillisLong long getBackOffHintMillis() { + return mBackOffHintMillis; + } }