Skip to content

Commit bf1287c

Browse files
authored
add smithy-go container LRU impl and debounce returns in v2 endpoint resolution (#443)
1 parent 3714cee commit bf1287c

File tree

7 files changed

+174
-15
lines changed

7 files changed

+174
-15
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "d2db122d-1fda-4d9e-85a8-f482949f7e3e",
3+
"type": "bugfix",
4+
"description": "Prevent duplicated error returns in EndpointResolverV2 default implementation.",
5+
"modules": [
6+
"."
7+
]
8+
}

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/ServiceGenerator.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,11 @@ private void writeConfigFieldResolvers(
122122
writer.writeInline(", opID");
123123
}
124124
if (resolver.isWithClientInput()) {
125-
writer.writeInline(", *c");
125+
if (resolver.getLocation() == ConfigFieldResolver.Location.CLIENT) {
126+
writer.writeInline(", client");
127+
} else {
128+
writer.writeInline(", *c");
129+
}
126130
}
127131
writer.write(")");
128132
writer.write("");
@@ -153,17 +157,17 @@ private void generateConstructor(Symbol serviceSymbol) {
153157
writer.openBlock("for _, fn := range optFns {", "}", () -> writer.write("fn(&options)"));
154158
writer.write("");
155159

160+
writer.openBlock("client := &$T{", "}", serviceSymbol, () -> {
161+
writer.write("options: options,");
162+
}).write("");
163+
156164
// Run any config finalization functions registered by runtime plugins.
157165
for (RuntimeClientPlugin plugin : plugins) {
158166
writeConfigFieldResolvers(writer, plugin, resolver ->
159167
resolver.getLocation() == ConfigFieldResolver.Location.CLIENT
160168
&& resolver.getTarget() == ConfigFieldResolver.Target.FINALIZATION);
161169
}
162170

163-
writer.openBlock("client := &$T{", "}", serviceSymbol, () -> {
164-
writer.write("options: options,");
165-
}).write("");
166-
167171
// Run any client member resolver functions registered by runtime plugins.
168172
for (RuntimeClientPlugin plugin : plugins) {
169173
writeClientMemberResolvers(writer, plugin, resolver -> true);

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -225,21 +225,17 @@ private GoWriter.Writable generateRulesList(List<Rule> rules, Scope scope) {
225225
});
226226

227227
if (!rules.isEmpty() && !(rules.get(rules.size() - 1).getConditions().isEmpty())) {
228-
// the rules tree can be constructed in a manner that tricks us
229-
// into writing this twice consecutively (we write it, return
230-
// to the outer generateRulesList call, and write it again
231-
// without having closed the outer parentheses
232-
//
233-
// ensure we write only once to avoid unreachable code, but
234-
// this should most likely be handled/fixed elsewhere
228+
// FIXME: there's currently a bug in traversal that sometimes causes subsequent returns to be generated
229+
// which fails vet. Patch over it via debounce for now
235230
ensureFinalTreeRuleError(w);
236231
}
237232
};
238233
}
239234

240235
private void ensureFinalTreeRuleError(GoWriter w) {
241-
final String expected = "return endpoint, fmt.Errorf(\"" + ERROR_MESSAGE_ENDOFTREE + "\")";
242-
if (!w.toString().trim().endsWith(expected)) {
236+
final String[] lines = w.toString().split("\n");
237+
final String lastLine = lines[lines.length - 1];
238+
if (!lastLine.trim().startsWith("return")) {
243239
w.writeGoTemplate(
244240
"return endpoint, $fmtErrorf:T(\"" + ERROR_MESSAGE_ENDOFTREE + "\")",
245241
commonCodegenArgs

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ConfigFieldResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public boolean isWithOperationName() {
6161
}
6262

6363
public boolean isWithClientInput() {
64-
return withClientInput && location == Location.OPERATION;
64+
return withClientInput;
6565
}
6666

6767
public static Builder builder() {

container/private/cache/cache.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Package cache defines the interface for a key-based data store.
2+
//
3+
// This package is designated as private and is intended for use only by the
4+
// smithy client runtime. The exported API therein is not considered stable and
5+
// is subject to breaking changes without notice.
6+
package cache
7+
8+
// Cache defines the interface for an opaquely-typed, key-based data store.
9+
//
10+
// The thread-safety of this interface is undefined and is dictated by
11+
// implementations.
12+
type Cache interface {
13+
// Retrieve the value associated with the given key. The returned boolean
14+
// indicates whether the cache held a value for the given key.
15+
Get(k interface{}) (interface{}, bool)
16+
17+
// Store a value under the given key.
18+
Put(k interface{}, v interface{})
19+
}

container/private/cache/lru/lru.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Package lru implements [cache.Cache] with an LRU eviction policy.
2+
//
3+
// This implementation is NOT thread-safe.
4+
//
5+
// This package is designated as private and is intended for use only by the
6+
// smithy client runtime. The exported API therein is not considered stable and
7+
// is subject to breaking changes without notice.
8+
package lru
9+
10+
import (
11+
"container/list"
12+
13+
"github.com/aws/smithy-go/container/private/cache"
14+
)
15+
16+
// New creates a new LRU cache with the given capacity.
17+
func New(cap int) cache.Cache {
18+
return &lru{
19+
entries: make(map[interface{}]*list.Element, cap),
20+
cap: cap,
21+
mru: list.New(),
22+
}
23+
}
24+
25+
type lru struct {
26+
entries map[interface{}]*list.Element
27+
cap int
28+
29+
mru *list.List // least-recently used is at the back
30+
}
31+
32+
type element struct {
33+
key interface{}
34+
value interface{}
35+
}
36+
37+
func (l *lru) Get(k interface{}) (interface{}, bool) {
38+
e, ok := l.entries[k]
39+
if !ok {
40+
return nil, false
41+
}
42+
43+
l.mru.MoveToFront(e)
44+
return e.Value.(*element).value, true
45+
}
46+
47+
func (l *lru) Put(k interface{}, v interface{}) {
48+
if len(l.entries) == l.cap {
49+
l.evict()
50+
}
51+
52+
ev := &element{
53+
key: k,
54+
value: v,
55+
}
56+
e := l.mru.PushFront(ev)
57+
l.entries[k] = e
58+
}
59+
60+
func (l *lru) evict() {
61+
e := l.mru.Remove(l.mru.Back())
62+
delete(l.entries, e.(*element).key)
63+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package lru
2+
3+
import "testing"
4+
5+
func TestCache(t *testing.T) {
6+
cache := New(4).(*lru)
7+
8+
// fill cache
9+
cache.Put(1, 2)
10+
cache.Put(2, 3)
11+
cache.Put(3, 4)
12+
cache.Put(4, 5)
13+
assertEntry(t, cache, 1, 2)
14+
assertEntry(t, cache, 2, 3)
15+
assertEntry(t, cache, 3, 4)
16+
assertEntry(t, cache, 4, 5)
17+
18+
// touch the last entry
19+
cache.Get(1)
20+
cache.Put(5, 6)
21+
assertNoEntry(t, cache, 2)
22+
assertEntry(t, cache, 3, 4)
23+
assertEntry(t, cache, 4, 5)
24+
assertEntry(t, cache, 1, 2)
25+
assertEntry(t, cache, 5, 6)
26+
27+
// put something new, 3 should now be the oldest
28+
cache.Put(6, 7)
29+
assertNoEntry(t, cache, 3)
30+
assertEntry(t, cache, 4, 5)
31+
assertEntry(t, cache, 1, 2)
32+
assertEntry(t, cache, 5, 6)
33+
assertEntry(t, cache, 6, 7)
34+
35+
// touch something in the middle
36+
cache.Get(5)
37+
assertEntry(t, cache, 4, 5)
38+
assertEntry(t, cache, 1, 2)
39+
assertEntry(t, cache, 5, 6)
40+
assertEntry(t, cache, 6, 7)
41+
42+
// put 3 new things, 5 should remain after the touch
43+
cache.Put(7, 8)
44+
cache.Put(8, 9)
45+
cache.Put(9, 0)
46+
assertNoEntry(t, cache, 4)
47+
assertNoEntry(t, cache, 1)
48+
assertNoEntry(t, cache, 6)
49+
assertEntry(t, cache, 5, 6)
50+
assertEntry(t, cache, 7, 8)
51+
assertEntry(t, cache, 8, 9)
52+
assertEntry(t, cache, 9, 0)
53+
}
54+
55+
func assertEntry(t *testing.T, c *lru, k interface{}, v interface{}) {
56+
e, ok := c.entries[k]
57+
if !ok {
58+
t.Errorf("expected entry %v=%v, but no entry found", k, v)
59+
}
60+
if actual := e.Value.(*element).value; actual != v {
61+
t.Errorf("expected entry %v=%v, but got entry value %v", k, v, actual)
62+
}
63+
}
64+
65+
func assertNoEntry(t *testing.T, c *lru, k interface{}) {
66+
if _, ok := c.Get(k); ok {
67+
t.Errorf("expected no entry for %v, but one was found", k)
68+
}
69+
}

0 commit comments

Comments
 (0)