Skip to content

Commit 9905e46

Browse files
Respect range during cache streaming (#169)
The core cache API allows for cache items to be concurrently streamed to / from the cache. Previously, if: - The size of the cached item's body was not provided by the writer - The reader requests a specific range of the cached item's body - The writer and reader are concurrent, i.e. the body is streamed from one to the other then the core cache API will ignore the requested range and provide the whole body. There is no explicit notification that the whole body is provided instead of a range. In this SDK release we change the default: body reads through the cache will use the requested range even when streaming. A lookup option allows setting the legacy behavior.
1 parent 9bed738 commit 9905e46

File tree

5 files changed

+101
-2
lines changed

5 files changed

+101
-2
lines changed

cache/core/core.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,27 @@ type LookupOptions struct {
258258
// To indicates the ending offset to read from the cached object. A
259259
// value of 0 means to read to the end of the object.
260260
To uint64
261+
262+
// LegacyReturnWholeBody restores a legacy behavior around range requests.
263+
//
264+
// In SDK v1.4.2 and earlier, under certain circumstances the requested range
265+
// (From/To) would be ignored. Specifically, if:
266+
// - The lookup (reader) is concurrent with the writer of the body
267+
// - The size of the cached item's body was not provided by the writer
268+
// - The reader requests a specific range of the cached item's body
269+
// (`From` and `To` are provided in LookupOptions or GetBodyOptions)
270+
// then the core cache API would ignore the requested range and provide
271+
// the entire body, instead of providing just the requested range.
272+
//
273+
// In SDK v1.4.3, the default behavior changed. In a concurrent read/write:
274+
// - If From and To are nonzero, the reader will block until the start of
275+
// the requested range has been provided by the writer.
276+
// - Only the requested range will be returned to the reader.
277+
//
278+
// Note that the full body is still provided if the range is invalid.
279+
//
280+
// LegacyReturnWholeBody restores the v1.4.2 behavior.
281+
LegacyReturnWholeBody bool
261282
}
262283

263284
func abiLookupOptions(opts LookupOptions) (fastly.CacheLookupOptions, error) {
@@ -272,6 +293,8 @@ func abiLookupOptions(opts LookupOptions) (fastly.CacheLookupOptions, error) {
272293
abiOpts.SetRequest(req)
273294
}
274295

296+
abiOpts.SetAlwaysUseRequestedRange(!opts.LegacyReturnWholeBody)
297+
275298
return abiOpts, nil
276299
}
277300

cache/core/core_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,64 @@ func ExampleFound_GetRange() {
208208

209209
fmt.Printf("The cached value was: %s", cachedStr)
210210
}
211+
212+
func ExampleLookupOptions_LegacyReturnWholeBody() {
213+
const (
214+
key = "my_key"
215+
contents = "my cached object"
216+
)
217+
218+
// Start an insert; this will be concurrent with the read.
219+
// Data written to this handle is streamed into the Fastly cache.
220+
w, err := core.Insert([]byte(key), core.WriteOptions{
221+
TTL: time.Hour,
222+
SurrogateKeys: []string{key},
223+
Length: uint64(len(contents)),
224+
})
225+
if err != nil {
226+
panic(err)
227+
}
228+
229+
// With the write still outstanding, start a lookup for a specific range.
230+
legacy, err := core.Lookup([]byte(key), core.LookupOptions{
231+
From: 3,
232+
To: 8,
233+
LegacyReturnWholeBody: true,
234+
})
235+
if err != nil {
236+
panic(err)
237+
}
238+
// With the write still outstanding, start a lookup for a specific range.
239+
updated, err := core.Lookup([]byte(key), core.LookupOptions{
240+
From: 3,
241+
To: 8,
242+
})
243+
if err != nil {
244+
panic(err)
245+
}
246+
247+
// The read and write are concurrent, and with an unknown length.
248+
// In the legacy mode, we'll see the whole body;
249+
// in the updated mode, we'll see just the range.
250+
251+
// Finish writing the body:
252+
if _, err := io.WriteString(w, contents); err != nil {
253+
panic(err)
254+
}
255+
256+
legacyStr, err := io.ReadAll(legacy.Body)
257+
if err != nil {
258+
panic(err)
259+
}
260+
updatedStr, err := io.ReadAll(updated.Body)
261+
if err != nil {
262+
panic(err)
263+
}
264+
265+
if got, want := string(legacyStr), "my cached object"; got != want {
266+
panic(fmt.Sprintf("got: %q, want: %q", got, want))
267+
}
268+
if got, want := string(updatedStr), "cached"; got != want {
269+
panic(fmt.Sprintf("got: %q, want: %q", got, want))
270+
}
271+
}

internal/abi/fastly/cache_guest.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ func (o *CacheLookupOptions) SetRequest(req *HTTPRequest) {
2121
o.mask |= cacheLookupOptionsMaskRequestHeaders
2222
}
2323

24+
func (o *CacheLookupOptions) SetAlwaysUseRequestedRange(alwaysUseRequestedRange bool) {
25+
if alwaysUseRequestedRange {
26+
o.mask |= cacheLookupOptionsMaskAlwaysUseRequestedRange
27+
} else {
28+
o.mask &= ^cacheLookupOptionsMaskAlwaysUseRequestedRange
29+
}
30+
}
31+
2432
type CacheGetBodyOptions struct {
2533
opts cacheGetBodyOptions
2634
mask cacheGetBodyOptionsMask

internal/abi/fastly/hostcalls_noguest.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,10 @@ func (o *CacheLookupOptions) SetRequest(req *HTTPRequest) error {
425425
return fmt.Errorf("not implemented")
426426
}
427427

428+
func (o *CacheLookupOptions) SetAlwaysUseRequestedRange(alwaysUseRequestedRange bool) error {
429+
return fmt.Errorf("not implemented")
430+
}
431+
428432
func (o *CacheGetBodyOptions) From(from uint64) error {
429433
return fmt.Errorf("not implemented")
430434
}

internal/abi/fastly/types.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -721,13 +721,16 @@ type cacheLookupOptions struct {
721721
// (flags (@witx repr u32)
722722
// $reserved
723723
// $request_headers
724+
// $service_id
725+
// $always_use_requested_range
724726
// )
725727
// )
726728
type cacheLookupOptionsMask prim.U32
727729

728730
const (
729-
cacheLookupOptionsMaskReserved cacheLookupOptionsMask = 0b0000_0001 // $reserved
730-
cacheLookupOptionsMaskRequestHeaders cacheLookupOptionsMask = 0b0000_0010 // $request_headers
731+
cacheLookupOptionsMaskReserved cacheLookupOptionsMask = 0b0000_0001 // $reserved
732+
cacheLookupOptionsMaskRequestHeaders cacheLookupOptionsMask = 0b0000_0010 // $request_headers
733+
cacheLookupOptionsMaskAlwaysUseRequestedRange cacheLookupOptionsMask = 0b0000_1000 // $always_use_requested_range
731734
)
732735

733736
// witx:

0 commit comments

Comments
 (0)