Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Store HTTP telemetry values on Actors to ensure thread safety #807

Closed
wants to merge 7 commits into from

Conversation

jbelkins
Copy link
Contributor

@jbelkins jbelkins commented Sep 4, 2024

Issue #

awslabs/aws-sdk-swift#1705

Description of changes

  • Isolate accesses to HttpTelemetry and associated types by converting them to actor.
  • Meter callbacks have been made async to allow asynchronous calls on actors within.
  • A couple non-async SDKLogging methods have been marked non-async.
  • Telemetry access from URLSessionHTTPClient is performed on a separate Task that is spawned as a new request is started.
  • Telemetry access from the CRT HTTP client is performed on the current task since it is already being performed from an async context.

Scope

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@jbelkins jbelkins marked this pull request as ready for review September 4, 2024 19:09
@@ -221,17 +221,17 @@ public class CRTClientEngine: HTTPClient {
// END - smithy.client.http.requests.queued_duration

// TICK - smithy.client.http.connections.limit
Copy link
Contributor Author

Choose a reason for hiding this comment

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

httpMetricsUsage is now an actor, so changes to it must be made with an async method call, not a synchronous setter.

@@ -10,7 +10,7 @@ import struct Smithy.AttributeKey
/// Container for HTTPClient telemetry, including configurable attributes and names.
///
/// Note: This is intended to be used within generated code, not directly.
public class HttpTelemetry {
public actor HttpTelemetry {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

HttpTelemetry is now an actor, which enforces exclusive access to its internal data.

@@ -160,10 +160,30 @@ internal enum HttpMetricsAttributesKeys {
internal static let serverAddress = AttributeKey<String>(name: "server.address")
}

internal struct HttpMetricsUsage {
internal actor HttpMetricsUsage {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

HttpMetricsUsage is now an actor, to enforce exclusive access to its properties.

Async setter methods are added for its properties since actor properties may not be set from outside the actor.

@@ -502,34 +502,37 @@ public final class URLSessionHTTPClient: HTTPClient {
body = .data(nil)
}

// Capture current time, then spawn a Task to write HTTP telemetry
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Code below is the same as before, except:

  • Accesses to telemetry and telemetry. httpMetricsUsage are now async since those properties are now actors.
  • Telemetry code is moved into a spawned Task, which allows for async operations and allows telemetry to be recorded concurrently with continuing the request.

@@ -72,7 +72,7 @@ extension DefaultTelemetry {
fileprivate final class NoOpMeter: Meter {
func createGauge(
name: String,
callback: @escaping (any DoubleAsyncMeasurement) -> Void,
callback: @escaping (any DoubleAsyncMeasurement) async -> Void,
Copy link
Contributor Author

@jbelkins jbelkins Sep 4, 2024

Choose a reason for hiding this comment

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

These callback blocks on DefaultTelemetry are now asynchronous, which allows the block to access properties on actors.

@@ -18,7 +18,7 @@ public actor SDKLoggingSystem {
factories[label] = logHandlerFactory
}

public func initialize(defaultLogLevel: SDKLogLevel = .error) async {
public func initialize(defaultLogLevel: SDKLogLevel = .error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function has no async operations within, and async is removed. Same for the init below.

@@ -28,7 +28,7 @@ public protocol Meter: Sendable {
/// - Returns: handle to stop recording Gauge metrics
func createGauge(
name: String,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Blocks for this protocol are now async to allow for the use of actors.

@@ -502,34 +502,41 @@ public final class URLSessionHTTPClient: HTTPClient {
body = .data(nil)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Values used by telemetry are captured, then a Task is spawned that does the writing of the telemetry data.
This allows for:

  • Request can proceed while telemetry is written
  • Task provides async context for access to actors.

@jbelkins
Copy link
Contributor Author

jbelkins commented Sep 5, 2024

Closing this PR because the use of Actors to hold telemetry data is incompatible with opentelemetry-swift, which does not generally have asynchronous APIs.

Will open a new PR which handles the data race in a way compatible with pre-Swift concurrency code.

@jbelkins jbelkins closed this Sep 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant