From 4638ce8e1faedeafa7d1a2f76f3b59f9f7bc9079 Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Mon, 30 Oct 2017 13:10:36 -0700 Subject: [PATCH] Periodically Invalidate Cached Items Previously, if there was an error when a cached item was first calculated, that error would be cached forever. This change adds a `cacheDuration()` method to the DefaultConnectionContext and DefaultCloudFoundryOperations in order to configure how long _anything_ should be cached. This could be something like how long the root payload is cached or the organization and space ids. The default is cached indefinitely, but can be configured. [#749] --- .../AbstractPayloadCachingRootProvider.java | 7 ++++-- .../reactor/AbstractRootProvider.java | 18 +++++++++----- .../reactor/ConnectionContext.java | 8 +++++++ .../reactor/_DefaultConnectionContext.java | 8 ++++--- .../AbstractUaaTokenProvider.java | 9 ++++--- .../reactor/uaa/_ReactorUaaClient.java | 9 ++++--- .../_DefaultCloudFoundryOperations.java | 24 ++++++++++++++----- 7 files changed, 60 insertions(+), 23 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractPayloadCachingRootProvider.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractPayloadCachingRootProvider.java index 5bdad93884d..661696d7e96 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractPayloadCachingRootProvider.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractPayloadCachingRootProvider.java @@ -46,8 +46,11 @@ protected final Mono doGetRoot(String key, ConnectionContext conn abstract ObjectMapper getObjectMapper(); private Mono> getPayload(ConnectionContext connectionContext) { - return doGetPayload(connectionContext) - .cache(); + Mono> cached = doGetPayload(connectionContext); + + return connectionContext.getCacheDuration() + .map(cached::cache) + .orElseGet(cached::cache); } } diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractRootProvider.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractRootProvider.java index dbee53c48e4..5c216b94e45 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractRootProvider.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/AbstractRootProvider.java @@ -52,18 +52,24 @@ public final void checkForValidApiHost() { @Override public final Mono getRoot(ConnectionContext connectionContext) { - return doGetRoot(connectionContext) + Mono cached = doGetRoot(connectionContext) .delayUntil(uri -> trust(uri.getHost(), uri.getPort(), connectionContext)) - .map(UriComponents::toUriString) - .cache(); + .map(UriComponents::toUriString); + + return connectionContext.getCacheDuration() + .map(cached::cache) + .orElseGet(cached::cache); } @Override public final Mono getRoot(String key, ConnectionContext connectionContext) { - return doGetRoot(key, connectionContext) + Mono cached = doGetRoot(key, connectionContext) .delayUntil(uri -> trust(uri.getHost(), uri.getPort(), connectionContext)) - .map(UriComponents::toUriString) - .cache(); + .map(UriComponents::toUriString); + + return connectionContext.getCacheDuration() + .map(cached::cache) + .orElseGet(cached::cache); } protected abstract Mono doGetRoot(ConnectionContext connectionContext); diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/ConnectionContext.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/ConnectionContext.java index 0813f29fb0a..ca792aa44f6 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/ConnectionContext.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/ConnectionContext.java @@ -20,11 +20,19 @@ import reactor.core.publisher.Mono; import reactor.ipc.netty.http.client.HttpClient; +import java.time.Duration; +import java.util.Optional; + /** * Common, reusable, connection context */ public interface ConnectionContext { + /** + * The duration that stable responses like the payload of the API root should be cached + */ + Optional getCacheDuration(); + /** * The {@link HttpClient} to use */ diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/_DefaultConnectionContext.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/_DefaultConnectionContext.java index 9d968a1bd8d..eaa42e414d1 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/_DefaultConnectionContext.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/_DefaultConnectionContext.java @@ -71,6 +71,9 @@ public final void dispose() { getThreadPool().dispose(); } + @Override + public abstract Optional getCacheDuration(); + /** * The number of connections to use when processing requests and responses. Setting this to `null` disables connection pooling. */ @@ -97,9 +100,8 @@ public HttpClient getHttpClient() { getConnectTimeout().ifPresent(socketTimeout -> options.option(CONNECT_TIMEOUT_MILLIS, (int) socketTimeout.toMillis())); getKeepAlive().ifPresent(keepAlive -> options.option(SO_KEEPALIVE, keepAlive)); getSslHandshakeTimeout().ifPresent(options::sslHandshakeTimeout); -// TODO: Add back once these options show up in 0.7.0 -// getSslCloseNotifyFlushTimeout().ifPresent(options::sslCloseNotifyFlushTimeout); -// getSslCloseNotifyReadTimeout().ifPresent(options::sslCloseNotifyReadTimeout); + getSslCloseNotifyFlushTimeout().ifPresent(options::sslCloseNotifyFlushTimeout); + getSslCloseNotifyReadTimeout().ifPresent(options::sslCloseNotifyReadTimeout); getProxyConfiguration().ifPresent(c -> c.configure(options)); }); } diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/tokenprovider/AbstractUaaTokenProvider.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/tokenprovider/AbstractUaaTokenProvider.java index 231e8a75ea3..227e437add7 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/tokenprovider/AbstractUaaTokenProvider.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/tokenprovider/AbstractUaaTokenProvider.java @@ -257,14 +257,17 @@ private Mono requestToken(ConnectionContext connectionContex } private Mono token(ConnectionContext connectionContext) { - return this.refreshTokens.getOrDefault(connectionContext, Mono.empty()) + Mono cached = this.refreshTokens.getOrDefault(connectionContext, Mono.empty()) .flatMap(refreshToken -> refreshToken(connectionContext, refreshToken) .doOnSubscribe(s -> LOGGER.debug("Negotiating using refresh token"))) .switchIfEmpty(primaryToken(connectionContext) .doOnSubscribe(s -> LOGGER.debug("Negotiating using token provider"))) .transform(ErrorPayloadMapper.fallback()) - .transform(extractTokens(connectionContext)) - .cache() + .transform(extractTokens(connectionContext)); + + return connectionContext.getCacheDuration() + .map(cached::cache) + .orElseGet(cached::cache) .checkpoint(); } diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java index 034cbcbd69d..ada85ac2558 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java @@ -107,9 +107,12 @@ public Users users() { @Value.Default Mono getRoot() { - return getConnectionContext().getRootProvider().getRoot("uaa", getConnectionContext()) - .map(getIdentityZoneEndpoint(getIdentityZoneSubdomain())) - .cache(); + Mono cached = getConnectionContext().getRootProvider().getRoot("uaa", getConnectionContext()) + .map(getIdentityZoneEndpoint(getIdentityZoneSubdomain())); + + return getConnectionContext().getCacheDuration() + .map(cached::cache) + .orElseGet(cached::cache); } /** diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java index e712a645bec..ef2ff840cab 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java @@ -56,6 +56,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.time.Duration; import java.util.NoSuchElementException; import java.util.Optional; @@ -137,6 +138,11 @@ public Stacks stacks() { return new DefaultStacks(getCloudFoundryClientPublisher()); } + /** + * The duration that stable responses like the organization and space id should be cached + */ + abstract Optional getCacheDuration(); + /** * The {@link CloudFoundryClient} to use for operations functionality */ @@ -174,9 +180,12 @@ Mono getOrganizationId() { String organization = getOrganization(); if (hasText(organization)) { - return getOrganization(getCloudFoundryClientPublisher(), organization) - .map(ResourceUtils::getId) - .cache(); + Mono cached = getOrganization(getCloudFoundryClientPublisher(), organization) + .map(ResourceUtils::getId); + + return getCacheDuration() + .map(cached::cache) + .orElseGet(cached::cache); } else { return Mono.error(new IllegalStateException("No organization targeted")); } @@ -206,10 +215,13 @@ Mono getSpaceId() { String space = getSpace(); if (hasText(getSpace())) { - return getOrganizationId() + Mono cached = getOrganizationId() .flatMap(organizationId -> getSpace(getCloudFoundryClientPublisher(), organizationId, space)) - .map(ResourceUtils::getId) - .cache(); + .map(ResourceUtils::getId); + + return getCacheDuration() + .map(cached::cache) + .orElseGet(cached::cache); } else { return Mono.error(new IllegalStateException("No space targeted")); }