Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.core.internal.crac.ClasspathWarmUpInvoker;
import software.amazon.awssdk.core.internal.http.loader.ClasspathHttpWarmupInvoker;

/**
* Entry point for warming up SDK service request paths before a Coordinated Restore at Checkpoint (CRaC)
Expand Down Expand Up @@ -66,6 +67,7 @@ public static void prime() {
}
// Set primed only after invokeAll() succeeds, so a failed run leaves primed false and a later call retries.
ClasspathWarmUpInvoker.create().invokeAll();
ClasspathHttpWarmupInvoker.create().invokeAll();
primed = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@

package software.amazon.awssdk.core.internal.crac;

import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.core.crac.SdkWarmUpProvider;
import software.amazon.awssdk.utils.Logger;

/**
* {@link WarmUpInvoker} implementation that uses {@link ServiceLoader} to find {@link SdkWarmUpProvider}
Expand All @@ -30,8 +27,6 @@
@SdkInternalApi
public final class ClasspathWarmUpInvoker implements WarmUpInvoker {

private static final Logger log = Logger.loggerFor(ClasspathWarmUpInvoker.class);

private final WarmUpServiceLoader serviceLoader;

@SdkTestInternalApi
Expand All @@ -41,30 +36,7 @@ public final class ClasspathWarmUpInvoker implements WarmUpInvoker {

@Override
public void invokeAll() {
Iterator<SdkWarmUpProvider> iterator = serviceLoader.loadProviders();
boolean invokedAny = false;

while (iterator.hasNext()) {
SdkWarmUpProvider provider;
try {
provider = iterator.next();
} catch (ServiceConfigurationError e) {
// next() has already advanced past the bad provider, so it is safe to continue to the next one.
log.warn(() -> "Skipping an SdkWarmUpProvider that could not be loaded.", e);
continue;
}

invokedAny = true;
try {
provider.warmUp();
} catch (RuntimeException e) {
log.warn(() -> "An SdkWarmUpProvider failed during warmUp() and was skipped.", e);
}
}

if (!invokedAny) {
log.debug(() -> "No SdkWarmUpProvider implementations were discovered on the classpath.");
}
WarmUpDiscovery.forEachDiscovered(serviceLoader.loadProviders(), SdkWarmUpProvider::warmUp);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.crac;

import java.net.URI;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.utils.OptionalUtils;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.awssdk.utils.SystemSetting;
import software.amazon.awssdk.utils.http.SdkHttpUtils;
import software.amazon.awssdk.utils.internal.SystemSettingUtils;

/**
* Resolves the regional STS endpoint ({@code https://sts.<region>.amazonaws.com/}) used by the CRaC HTTP-client warm-up.
*
Comment on lines +29 to +30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this class intended to resolve other service endpoints in the future?

* <p>The region is taken from the first of: {@link SdkSystemSetting#AWS_REGION} (the {@code aws.region} system property or
* {@code AWS_REGION} environment variable), the {@code AWS_DEFAULT_REGION} environment variable, or {@value #DEFAULT_REGION}.
*
* <p>Only system properties and environment variables are read. The full SDK region-resolution chain (IMDS, profile file) is
* avoided during priming because those add network or filesystem calls that may fail or time out. The endpoint host always
* uses the {@code amazonaws.com} suffix, which is incorrect for the China, GovCloud, and ISO partitions; in those partitions
* the warm-up request simply fails and is ignored, since it is best-effort.
*/
@SdkInternalApi
public final class RegionEndpointResolver {

static final String DEFAULT_REGION = "us-east-1";

private RegionEndpointResolver() {
}

public static RegionEndpointResolver create() {
return new RegionEndpointResolver();
}

/**
* @return the regional STS endpoint URI for the resolved region; never null.
*/
public URI stsEndpoint() {
// URL-encode the region before putting it in the host, same as Region.of(String).
return URI.create("https://sts." + SdkHttpUtils.urlEncode(resolveRegion()) + ".amazonaws.com/");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might benefit from using

public static void validateHostnameCompliant(String hostnameComponent, String paramName, String object) {
validateHostnameCompliant(hostnameComponent, paramName, object, DEFAULT_HOSTNAME_COMPLIANT_PATTERN);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively, build using the URI constructor will also validate it for correctness

}

private String resolveRegion() {
Optional<String> awsRegion = trimmed(SdkSystemSetting.AWS_REGION.getStringValue());
return OptionalUtils.firstPresent(awsRegion, RegionEndpointResolver::awsDefaultRegion)
.orElse(DEFAULT_REGION);
}

private static Optional<String> awsDefaultRegion() {
return trimmed(SystemSettingUtils.resolveEnvironmentVariable(new AwsDefaultRegionEnvVar()));
}

private static Optional<String> trimmed(Optional<String> value) {
// trimToNull returns null for blank/empty input, so Optional.map collapses those to an empty Optional.
return value.map(StringUtils::trimToNull);
}

// AWS_DEFAULT_REGION is an environment-variable-only fallback with no system-property equivalent.
private static final class AwsDefaultRegionEnvVar implements SystemSetting {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, we don't use AWS_DEFAULT_REGION for determining endpoints in the SDK (only DefaultsMode). Should we leave this out?

@Override
public String property() {
return null;
}

@Override
public String environmentVariable() {
return "AWS_DEFAULT_REGION";
}

@Override
public String defaultValue() {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.crac;

import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.function.Consumer;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.utils.Logger;

/**
* Shared best-effort {@link java.util.ServiceLoader} iteration for the CRaC warm-up paths.
*/
@SdkInternalApi
public final class WarmUpDiscovery {

private static final Logger log = Logger.loggerFor(WarmUpDiscovery.class);

private WarmUpDiscovery() {
}

/**
* Applies {@code action} to every discovered element, skipping (and logging at warn) any element that fails to load or
* whose action throws, so the rest still run. Logs at debug when nothing is discovered.
*/
public static <T> void forEachDiscovered(Iterator<T> iterator, Consumer<T> action) {
boolean discoveredAny = false;
while (iterator.hasNext()) {
T element;
try {
element = iterator.next();
} catch (ServiceConfigurationError e) {
// next() has already advanced past the bad element, so it is safe to continue to the next one.
log.warn(() -> "Skipping a warm-up task that could not be loaded.", e);
continue;
}

discoveredAny = true;
T discovered = element;
try {
action.accept(discovered);
} catch (RuntimeException | LinkageError e) {
// LinkageError because a discovered element can fail to link (missing deps/native lib, failed static init),
// which is an Error, not an Exception. Skip it to keep warm-up best-effort; fatal Errors still propagate.
log.warn(() -> "Warm-up failed for " + discovered.getClass().getName() + " and was skipped.", e);
}
}

if (!discoveredAny) {
log.debug(() -> "No warm-up tasks were discovered on the classpath.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.crac;

import java.net.URI;
import java.util.Optional;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.http.ContentStreamProvider;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;

/**
* Describes the request the CRaC warm-up sends to the resolved endpoint, and converts it to an {@link SdkHttpRequest} bound to
* that endpoint. The request is never signed and needs no credentials; only its execution (DNS, TLS, request/response I/O)
* matters for JIT priming.
*/
@SdkInternalApi
public final class WarmUpRequest {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure about the usefulness of this class. Does it make more sense to just have a utility class/factory just creates the HttpExecuteRequest directly?


private final SdkHttpMethod method;
private final String body;
private final String contentType;
Comment on lines +34 to +35

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ctor is private; are these necessary?


private WarmUpRequest(SdkHttpMethod method, String body, String contentType) {
this.method = method;
this.body = body;
this.contentType = contentType;
}

/**
* A bare {@code GET} to the endpoint, with no body. Warms DNS, the TLS handshake, and certificate-chain validation, which
* are the dominant cold-start costs and are shared across all services and operations.
*/
public static WarmUpRequest get() {
return new WarmUpRequest(SdkHttpMethod.GET, null, null);
}

/**
* @retusrn this request as an {@link SdkHttpRequest} targeting {@code endpoint}.
*/
public SdkHttpRequest toHttpRequest(URI endpoint) {
SdkHttpRequest.Builder builder = SdkHttpRequest.builder()
.method(method)
.uri(endpoint);
if (body != null) {
builder.putHeader("Content-Type", contentType)
.putHeader("Content-Length", String.valueOf(body.getBytes(java.nio.charset.StandardCharsets.UTF_8).length));
}
return builder.build();
}

/**
* @return the request body stream provider, or empty if this request has no body.
*/
public Optional<ContentStreamProvider> contentStreamProvider() {
return body == null ? Optional.empty() : Optional.of(ContentStreamProvider.fromUtf8String(body));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.http.loader;

import java.util.Collections;
import java.util.List;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;

/**
* {@link HttpWarmupInvoker} that owns the set of {@link HttpClientWarmer}s and invokes each.
*/
@SdkInternalApi
public final class ClasspathHttpWarmupInvoker implements HttpWarmupInvoker {

private final List<HttpClientWarmer> warmers;

@SdkTestInternalApi
ClasspathHttpWarmupInvoker(List<HttpClientWarmer> warmers) {
this.warmers = warmers;
}

/**
* @return an invoker over the HTTP-client warmers on the classpath.
*/
public static HttpWarmupInvoker create() {
return new ClasspathHttpWarmupInvoker(Collections.singletonList(SyncHttpClientWarmer.create()));
}

@Override
public void invokeAll() {
for (HttpClientWarmer warmer : warmers) {
warmer.warmAll();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.core.internal.http.loader;

import software.amazon.awssdk.annotations.SdkInternalApi;

/**
* Warms the sync or async HTTP clients on the classpath for CRaC priming.
*/
@SdkInternalApi
public interface HttpClientWarmer {

/**
* Warms every HTTP client found on the classpath. Best-effort; never throws.
*/
void warmAll();
}
Loading
Loading