Skip to content

Commit 47cce84

Browse files
authored
feat!: implement client config overrides for one or more operations (#776)
1 parent f8342c3 commit 47cce84

File tree

39 files changed

+449
-130
lines changed

39 files changed

+449
-130
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "6d4356bb-d28e-45e9-b025-7adcfa94db68",
3+
"type": "misc",
4+
"description": "**Breaking** Remove `Closeable` supertype from `HttpClientEngine` interface. See [this discussion post](https://github.com/awslabs/aws-sdk-kotlin/discussions/818) for more information."
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "f071b519-bd67-402b-826a-ced0a5b4c20d",
3+
"type": "feature",
4+
"description": "Allow config override for one or more operations with an existing service client."
5+
}

runtime/auth/aws-credentials/common/src/aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55
package aws.smithy.kotlin.runtime.auth.awscredentials
66

7+
import aws.smithy.kotlin.runtime.io.Closeable
8+
79
/**
810
* Represents a producer/source of AWS credentials
911
*/
@@ -13,3 +15,11 @@ public interface CredentialsProvider {
1315
*/
1416
public suspend fun getCredentials(): Credentials
1517
}
18+
19+
/**
20+
* A [CredentialsProvider] with [Closeable] resources. Users SHOULD call [close] when done with the provider to ensure
21+
* any held resources are properly released.
22+
*
23+
* Implementations SHOULD evict any previously-retrieved or stored credentials when the provider is closed.
24+
*/
25+
public interface CloseableCredentialsProvider : CredentialsProvider, Closeable

runtime/auth/aws-signing-tests/common/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/MiddlewareSigningTestBase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public abstract class MiddlewareSigningTestBase : HasSigner {
8888
return HttpCall(request, resp, now, now)
8989
}
9090
}
91-
val client = sdkHttpClient(mockEngine)
91+
val client = SdkHttpClient(mockEngine)
9292

9393
operation.roundTrip(client, Unit)
9494
return operation.context[HttpOperationContext.HttpCallList].last().request

runtime/auth/aws-signing-tests/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/tests/SigningSuiteTestBaseJVM.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ public actual abstract class SigningSuiteTestBase : HasSigner {
259259
return HttpCall(request, resp, now, now)
260260
}
261261
}
262-
val client = sdkHttpClient(mockEngine)
262+
val client = SdkHttpClient(mockEngine)
263263

264264
operation.roundTrip(client, Unit)
265265
return operation.context[HttpOperationContext.HttpCallList].last().request

runtime/io/common/src/aws/smithy/kotlin/runtime/io/Closeable.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package aws.smithy.kotlin.runtime.io
77

8+
import aws.smithy.kotlin.runtime.util.InternalApi
9+
810
// this really should live in the stdlib...
911
// https://youtrack.jetbrains.com/issue/KT-31066
1012

@@ -43,3 +45,8 @@ public inline fun <C : Closeable, R> C.use(block: (C) -> R): R {
4345
}
4446
}
4547
}
48+
49+
@InternalApi
50+
public fun Any.closeIfCloseable() {
51+
if (this is Closeable) close()
52+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.io
6+
7+
import aws.smithy.kotlin.runtime.util.InternalApi
8+
import kotlinx.atomicfu.locks.SynchronizedObject
9+
import kotlinx.atomicfu.locks.synchronized
10+
11+
/**
12+
* Interface that the lifecycle of some resource is managed by the SDK at runtime.
13+
*/
14+
@InternalApi
15+
public interface SdkManaged {
16+
/**
17+
* Invoked by a caller to declare usership of the resource.
18+
*/
19+
public fun share()
20+
21+
/**
22+
* Invoked when a caller releases the resource. Returns a boolean indicating whether the resource has been fully
23+
* unshared with this call (i.e. the caller was the only remaining user). Future calls on a fully unshared object
24+
* would return false.
25+
*/
26+
public fun unshare(): Boolean
27+
}
28+
29+
/**
30+
* Abstract class which implements usage count tracking for [SdkManaged].
31+
*/
32+
@InternalApi
33+
public abstract class SdkManagedBase : SdkManaged {
34+
private val state = object : SynchronizedObject() {
35+
var shareCount: Int = 0
36+
var isUnshared: Boolean = false
37+
}
38+
39+
override fun share() {
40+
synchronized(state) {
41+
check(!state.isUnshared) { "caller attempted to share() a fully unshared object" }
42+
43+
state.shareCount++
44+
}
45+
}
46+
47+
override fun unshare(): Boolean = synchronized(state) {
48+
if (state.isUnshared) return false
49+
50+
state.shareCount--
51+
if (state.shareCount > 0) return false
52+
53+
state.isUnshared = true
54+
true
55+
}
56+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.io
6+
7+
import aws.smithy.kotlin.runtime.util.InternalApi
8+
9+
/**
10+
* Wraps a [Closeable] to operate as an [SdkManaged] with share count tracking.
11+
* The final [unshare] call will trigger the closing of the resource.
12+
*/
13+
@InternalApi
14+
public open class SdkManagedCloseable(private val closeable: Closeable) : SdkManagedBase() {
15+
override fun unshare(): Boolean {
16+
val shouldClose = super.unshare()
17+
if (shouldClose) closeable.close()
18+
return shouldClose
19+
}
20+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.io
6+
7+
import aws.smithy.kotlin.runtime.util.InternalApi
8+
9+
/**
10+
* A collection of opaque resources that implement [SdkManaged].
11+
*/
12+
@InternalApi
13+
public class SdkManagedGroup(
14+
private val resources: MutableList<SdkManaged> = mutableListOf(),
15+
) {
16+
public fun add(resource: SdkManaged) {
17+
resource.share()
18+
resources.add(resource)
19+
}
20+
21+
public fun unshareAll() {
22+
resources.forEach { it.unshare() }
23+
}
24+
}
25+
26+
/**
27+
* Delegate extension to add a resource to the group if applicable.
28+
*/
29+
@InternalApi
30+
public fun SdkManagedGroup.addIfManaged(resource: Any) {
31+
if (resource is SdkManaged) add(resource)
32+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.io
6+
7+
import kotlin.test.*
8+
9+
class SdkManagedCloseableTest {
10+
class MockCloseableImpl : Closeable {
11+
var isClosed = false
12+
var closeCount = 0
13+
override fun close() {
14+
isClosed = true
15+
++closeCount
16+
}
17+
}
18+
19+
@Test
20+
fun testShareCount() {
21+
val closeable = MockCloseableImpl()
22+
val wrapped = SdkManagedCloseable(closeable)
23+
24+
wrapped.share()
25+
wrapped.share()
26+
wrapped.unshare()
27+
assertFalse(closeable.isClosed)
28+
}
29+
30+
@Test
31+
fun testCloseNoShare() {
32+
val closeable = MockCloseableImpl()
33+
val wrapped = SdkManagedCloseable(closeable)
34+
35+
wrapped.unshare()
36+
assertTrue(closeable.isClosed)
37+
}
38+
39+
@Test
40+
fun testCloseWithShare() {
41+
val closeable = MockCloseableImpl()
42+
val wrapped = SdkManagedCloseable(closeable)
43+
44+
wrapped.share()
45+
wrapped.unshare()
46+
assertTrue(closeable.isClosed)
47+
}
48+
49+
@Test
50+
fun testInnerCloseIdempotent() {
51+
val closeable = MockCloseableImpl()
52+
val wrapped = SdkManagedCloseable(closeable)
53+
54+
wrapped.share()
55+
wrapped.unshare()
56+
wrapped.unshare()
57+
assertTrue(closeable.isClosed)
58+
assertEquals(1, closeable.closeCount)
59+
}
60+
61+
@Test
62+
fun testShareClosedResource() {
63+
val closeable = MockCloseableImpl()
64+
val wrapped = SdkManagedCloseable(closeable)
65+
66+
wrapped.share()
67+
wrapped.unshare()
68+
val ex = assertFailsWith<IllegalStateException> {
69+
wrapped.share()
70+
}
71+
assertEquals("caller attempted to share() a fully unshared object", ex.message)
72+
}
73+
}

0 commit comments

Comments
 (0)