Skip to content

Commit a650902

Browse files
authored
Merge pull request #120 from fastly/cw/config-store-size
2 parents 5cd2713 + adc99e1 commit a650902

File tree

9 files changed

+138
-58
lines changed

9 files changed

+138
-58
lines changed

.github/workflows/integration-tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ jobs:
1414
# Newest supported configuration
1515
- go-version: '1.22' # pairs with TinyGo 0.31.2
1616
tinygo-version: '0.31.2'
17+
wasmtime-version: 'latest'
1718
# Oldest supported configuration
1819
- go-version: '1.21' # pairs with TinyGo 0.29.0
1920
tinygo-version: '0.29.0'
21+
wasmtime-version: '21.0.1' # pairs with TinyGo 0.29.0
2022

2123
steps:
2224
- uses: actions/checkout@v4
@@ -44,6 +46,8 @@ jobs:
4446
4547
- name: Set up Wasmtime
4648
uses: bytecodealliance/actions/wasmtime/setup@v1
49+
with:
50+
version: ${{ matrix.wasmtime-version }}
4751

4852
- name: Check our dependencies
4953
run: |

configstore/configstore.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ func Open(name string) (*Store, error) {
6161
return &Store{d}, nil
6262
}
6363

64-
// Has checks to see if the item exists in the config store, without allocating
65-
// any space to read it.
64+
// Has returns true if the key exists in the config store, without allocating
65+
// space to read a value.
6666
func (s *Store) Has(key string) (bool, error) {
6767
if s == nil {
6868
return false, ErrKeyNotFound
@@ -86,7 +86,7 @@ func (s *Store) Has(key string) (bool, error) {
8686
return v, nil
8787
}
8888

89-
// Get returns the item in the config store with the given key, as a byte slice.
89+
// GetBytes returns the value in the config store for the given key, if it exists, as a byte slice.
9090
func (s *Store) GetBytes(key string) ([]byte, error) {
9191
if s == nil {
9292
return nil, ErrKeyNotFound
@@ -106,16 +106,14 @@ func (s *Store) GetBytes(key string) ([]byte, error) {
106106
return nil, err
107107
}
108108
}
109-
110109
return v, nil
111110
}
112111

113-
// Get returns the item in the config store with the given key.
112+
// Get returns the value in the config store with the given key, if it exists.
114113
func (s *Store) Get(key string) (string, error) {
115114
buf, err := s.GetBytes(key)
116115
if err != nil {
117116
return "", err
118117
}
119-
120118
return string(buf), nil
121119
}

integration_tests/config_store/configstore.json

Lines changed: 10 additions & 1 deletion
Large diffs are not rendered by default.

integration_tests/config_store/main_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ package main
66

77
import (
88
"bytes"
9+
"strconv"
10+
"strings"
11+
"sync"
912
"testing"
1013

1114
"github.com/fastly/compute-sdk-go/configstore"
@@ -60,4 +63,56 @@ func TestConfigStore(t *testing.T) {
6063
if got, want := twitter, "https://twitter.com/fastly"; got != want {
6164
t.Errorf("Body = %q, want %q", got, want)
6265
}
66+
67+
maxKey := "maximum-length-asciiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii-1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
68+
max, err := d.Get(maxKey)
69+
if err != nil {
70+
t.Fatal(err)
71+
}
72+
73+
got := max
74+
want := "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
75+
if got != want {
76+
t.Errorf("Body = %q, want %q", got, want)
77+
}
78+
79+
maxUTF8, err := d.Get(strings.Repeat("ゝ", 255))
80+
if err != nil {
81+
t.Fatal(err)
82+
}
83+
84+
got, want = maxUTF8, strings.Repeat("ゝ", 8000)
85+
if got != want {
86+
t.Errorf("Body = %q, want %q", got, want)
87+
}
88+
}
89+
90+
func TestConfigStoreConcurrently(t *testing.T) {
91+
d, err := configstore.Open("configstore")
92+
if err != nil {
93+
t.Fatal(err)
94+
}
95+
var wg sync.WaitGroup
96+
wg.Add(5)
97+
var keys [5]string
98+
var values [5][]byte
99+
for i := 0; i < 5; i++ {
100+
go func(i int) {
101+
defer wg.Done()
102+
var err error
103+
keys[i] = "concurrent " + strconv.Itoa(i)
104+
values[i], err = d.GetBytes(keys[i])
105+
if err != nil {
106+
t.Errorf("%d: GetBytes() error: %v", i, err)
107+
}
108+
}(i)
109+
}
110+
wg.Wait()
111+
for i := 0; i < 5; i++ {
112+
got := values[i]
113+
want := bytes.Repeat([]byte{'0' + byte(i)}, i+1)
114+
if !bytes.Equal(got, want) {
115+
t.Errorf("%d: got: %q want: %q", i, got, want)
116+
}
117+
}
63118
}

integration_tests/request_upstream/fastly.toml

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,15 @@ manifest_version = 2
88
name = "request_upstream"
99
service_id = ""
1010

11-
[local_server]
1211

13-
[local_server.backends]
12+
[local_server.backends.TheOrigin]
13+
url = "https://compute-sdk-test-backend.edgecompute.app/"
1414

15-
[local_server.backends.TheOrigin]
16-
url = "https://compute-sdk-test-backend.edgecompute.app/"
15+
[local_server.backends.TheOrigin2]
16+
url = "https://compute-sdk-test-backend.edgecompute.app/"
1717

18-
[local_server.backends.TheOrigin2]
19-
url = "https://compute-sdk-test-backend.edgecompute.app/"
18+
[local_server.backends.example_backend]
19+
url = "https://example.org/"
2020

21-
[local_server.backends.example_backend]
22-
url = "https://example.org/"
23-
24-
[local_server.backends.httpme]
25-
url = "https://http-me.glitch.me"
21+
[local_server.backends.httpedge]
22+
url = "https://http.edgecompute.app/anything/"

integration_tests/request_upstream/main_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const bodySize = 64 * 1024
8787
func TestRequestUpstreamBody(t *testing.T) {
8888
body := make([]byte, bodySize)
8989
for i := range body {
90-
body[i] = byte(i)
90+
body[i] = 'A'
9191
}
9292

9393
b, err := fastly.NewHTTPBody()
@@ -124,15 +124,15 @@ func TestRequestUpstreamBody(t *testing.T) {
124124
}
125125

126126
func requestUpstreamBody(t *testing.T, body io.Reader, size int, chunked bool) {
127-
req, err := fsthttp.NewRequest("POST", "https://http-me.glitch.me/?anything", body)
127+
req, err := fsthttp.NewRequest("POST", "https://http.edgecompute.app/anything/", body)
128128
if err != nil {
129129
t.Fatalf("NewRequest: %v", err)
130130
}
131131

132132
req.Header.Set("Content-Type", "application/octet-stream")
133133
req.CacheOptions.Pass = true
134134

135-
resp, err := req.Send(context.Background(), "httpme")
135+
resp, err := req.Send(context.Background(), "httpedge")
136136
if err != nil {
137137
t.Fatalf("Send: %v", err)
138138
}
@@ -142,8 +142,9 @@ func requestUpstreamBody(t *testing.T, body io.Reader, size int, chunked bool) {
142142
Headers map[string]string `json:"headers"`
143143
}
144144

145-
if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil {
146-
t.Fatalf("Decode: %v", err)
145+
gotBody := new(bytes.Buffer)
146+
if err := json.NewDecoder(io.TeeReader(resp.Body, gotBody)).Decode(&respData); err != nil {
147+
t.Fatalf("Decode: %v\nBody:\n%s", err, gotBody.String())
147148
}
148149

149150
var teWant, clWant string

internal/abi/fastly/hostcalls_guest.go

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"math"
1212
"net"
1313
"strings"
14+
"sync"
1415
"time"
1516

1617
"github.com/fastly/compute-sdk-go/internal/abi/prim"
@@ -2436,10 +2437,27 @@ func fastlyDictionaryOpen(
24362437
// Dictionary represents a Fastly edge dictionary, a collection of read-only
24372438
// key/value pairs. For convenience, keys are modeled as Go strings, and values
24382439
// as byte slices.
2440+
//
2441+
// NOTE: wasm, by definition, is a single-threaded execution environment. This
2442+
// allows us to use valueBuf scratch space between the guest and host to avoid
2443+
// allocations any larger than necessary, without locking.
24392444
type Dictionary struct {
24402445
h dictionaryHandle
2446+
2447+
mu sync.Mutex // protects valueBuf
2448+
valueBuf [dictionaryMaxValueLen]byte
24412449
}
24422450

2451+
// Dictionaries are subject to very specific limitations: 255 character keys and 8000 character values, utf-8 encoded.
2452+
// The current storage collation limits utf-8 representations to 3 bytes in length.
2453+
// https://docs.fastly.com/en/guides/about-edge-dictionaries#limitations-and-considerations
2454+
// https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-utf8mb3.html
2455+
// https://en.wikipedia.org/wiki/UTF-8#Encoding
2456+
const (
2457+
dictionaryMaxKeyLen = 255 * 3 // known maximum size for config store keys: 755 bytes, for 255 3-byte utf-8 encoded characters
2458+
dictionaryMaxValueLen = 8000 * 3 // known maximum size for config store values: 24,000 bytes, for 8000 3-byte utf-8 encoded characters
2459+
)
2460+
24432461
// OpenDictionary returns a reference to the named dictionary, if it exists.
24442462
func OpenDictionary(name string) (*Dictionary, error) {
24452463
var d Dictionary
@@ -2452,7 +2470,6 @@ func OpenDictionary(name string) (*Dictionary, error) {
24522470
).toError(); err != nil {
24532471
return nil, err
24542472
}
2455-
24562473
return &d, nil
24572474
}
24582475

@@ -2477,42 +2494,42 @@ func fastlyDictionaryGet(
24772494
nWritten prim.Pointer[prim.Usize],
24782495
) FastlyStatus
24792496

2480-
// Get the value for key, as a byte slice, if it exists.
2481-
func (d *Dictionary) GetBytes(key string) ([]byte, error) {
2482-
keyBuffer := prim.NewReadBufferFromString(key).Wstring()
2483-
n := DefaultMediumBufLen // Longest (8192) = Config Store limit; typical values likely less than 1024
2484-
for {
2485-
buf := prim.NewWriteBuffer(n)
2486-
status := fastlyDictionaryGet(
2487-
d.h,
2488-
keyBuffer.Data, keyBuffer.Len,
2489-
prim.ToPointer(buf.Char8Pointer()), buf.Cap(),
2490-
prim.ToPointer(buf.NPointer()),
2491-
)
2492-
if status == FastlyStatusBufLen && n < dictionaryValueMaxLen {
2493-
// The Dictionary API cannot return the needed size with this error.
2494-
// Instead of perfectly adapting, we allocate the maximum length a value can have.
2495-
n = dictionaryValueMaxLen
2496-
continue
2497-
}
2498-
if err := status.toError(); err != nil {
2499-
return nil, err
2500-
}
2501-
return buf.AsBytes(), nil
2497+
// Get the value for key, if it exists. The returned slice's backing array is
2498+
// shared between multiple calls to getBytesUnlocked.
2499+
func (d *Dictionary) getBytesUnlocked(key string) ([]byte, error) {
2500+
keyBuffer := prim.NewReadBufferFromString(key)
2501+
if keyBuffer.Len() > dictionaryMaxKeyLen {
2502+
return nil, FastlyStatusInval.toError()
2503+
}
2504+
buf := prim.NewWriteBufferFromBytes(d.valueBuf[:]) // fresh slice of backing array
2505+
keyStr := keyBuffer.Wstring()
2506+
status := fastlyDictionaryGet(
2507+
d.h,
2508+
keyStr.Data, keyStr.Len,
2509+
prim.ToPointer(buf.Char8Pointer()), buf.Cap(),
2510+
prim.ToPointer(buf.NPointer()),
2511+
)
2512+
if err := status.toError(); err != nil {
2513+
return nil, err
25022514
}
2515+
return buf.AsBytes(), nil
25032516
}
25042517

2505-
// Get the value for key, if it exists.
2506-
func (d *Dictionary) Get(key string) (string, error) {
2507-
buf, err := d.GetBytes(key)
2518+
// GetBytes returns a slice of newly-allocated memory for the value
2519+
// corresponding to key.
2520+
func (d *Dictionary) GetBytes(key string) ([]byte, error) {
2521+
d.mu.Lock()
2522+
defer d.mu.Unlock()
2523+
v, err := d.getBytesUnlocked(key)
25082524
if err != nil {
2509-
return "", err
2525+
return nil, err
25102526
}
2511-
2512-
return string(buf), nil
2527+
p := make([]byte, len(v))
2528+
copy(p, v)
2529+
return p, nil
25132530
}
25142531

2515-
// Has returns whether a value exists.
2532+
// Has returns true if key is found.
25162533
func (d *Dictionary) Has(key string) (bool, error) {
25172534
keyBuffer := prim.NewReadBufferFromString(key).Wstring()
25182535
var npointer prim.Usize = 0

internal/abi/fastly/types.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,11 @@ func IsFastlyError(err error) (FastlyStatus, bool) {
185185
}
186186

187187
const (
188-
ipBufLen = 16 // known size for IP address buffers
189-
dnsBufLen = 256 // known size for "DNS" values, enough to hold the longest possible hostname or domain name
190-
dictionaryValueMaxLen = 8192 // known size for maximum config store value https://docs.fastly.com/en/guides/about-edge-dictionaries#limitations-and-considerations
188+
ipBufLen = 16 // known size for IP address buffers
189+
dnsBufLen = 256 // known size for "DNS" values, enough to hold the longest possible hostname or domain name
191190

192191
DefaultSmallBufLen = 128 // default size for "typically-small" values with variable sizes: HTTP methods, header names, tls protocol names, cipher suites
193-
DefaultMediumBufLen = 1024 // default size for values between small and large with variable sizes
192+
DefaultMediumBufLen = 1024 // default size for values between small and large, with variable sizes
194193
DefaultLargeBufLen = 8192 // default size for "typically-large" values with variable sizes; header values, URLs.
195194
)
196195

internal/abi/prim/prim.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func (b *WriteBuffer) NValue() Usize {
115115

116116
// AsBytes returns a slice of the buffer's data as a byte slice.
117117
func (b *WriteBuffer) AsBytes() []byte {
118-
return b.buf[:b.n]
118+
return b.buf[:b.n:b.n]
119119
}
120120

121121
// ToString returns a copy of the buffer's data as a string.

0 commit comments

Comments
 (0)