diff --git a/cache/core/core.go b/cache/core/core.go index 9261668..3373ac9 100644 --- a/cache/core/core.go +++ b/cache/core/core.go @@ -258,6 +258,27 @@ type LookupOptions struct { // To indicates the ending offset to read from the cached object. A // value of 0 means to read to the end of the object. To uint64 + + // LegacyReturnWholeBody restores a legacy behavior around range requests. + // + // In SDK v1.4.2 and earlier, under certain circumstances the requested range + // (From/To) would be ignored. Specifically, if: + // - The lookup (reader) is concurrent with the writer of the body + // - 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 + // (`From` and `To` are provided in LookupOptions or GetBodyOptions) + // then the core cache API would ignore the requested range and provide + // the entire body, instead of providing just the requested range. + // + // In SDK v1.4.3, the default behavior changed. In a concurrent read/write: + // - If From and To are nonzero, the reader will block until the start of + // the requested range has been provided by the writer. + // - Only the requested range will be returned to the reader. + // + // Note that the full body is still provided if the range is invalid. + // + // LegacyReturnWholeBody restores the v1.4.2 behavior. + LegacyReturnWholeBody bool } func abiLookupOptions(opts LookupOptions) (fastly.CacheLookupOptions, error) { @@ -272,6 +293,8 @@ func abiLookupOptions(opts LookupOptions) (fastly.CacheLookupOptions, error) { abiOpts.SetRequest(req) } + abiOpts.SetAlwaysUseRequestedRange(!opts.LegacyReturnWholeBody) + return abiOpts, nil } diff --git a/cache/core/core_test.go b/cache/core/core_test.go index c359eec..871dda2 100644 --- a/cache/core/core_test.go +++ b/cache/core/core_test.go @@ -208,3 +208,64 @@ func ExampleFound_GetRange() { fmt.Printf("The cached value was: %s", cachedStr) } + +func ExampleLookupOptions_LegacyReturnWholeBody() { + const ( + key = "my_key" + contents = "my cached object" + ) + + // Start an insert; this will be concurrent with the read. + // Data written to this handle is streamed into the Fastly cache. + w, err := core.Insert([]byte(key), core.WriteOptions{ + TTL: time.Hour, + SurrogateKeys: []string{key}, + Length: uint64(len(contents)), + }) + if err != nil { + panic(err) + } + + // With the write still outstanding, start a lookup for a specific range. + legacy, err := core.Lookup([]byte(key), core.LookupOptions{ + From: 3, + To: 8, + LegacyReturnWholeBody: true, + }) + if err != nil { + panic(err) + } + // With the write still outstanding, start a lookup for a specific range. + updated, err := core.Lookup([]byte(key), core.LookupOptions{ + From: 3, + To: 8, + }) + if err != nil { + panic(err) + } + + // The read and write are concurrent, and with an unknown length. + // In the legacy mode, we'll see the whole body; + // in the updated mode, we'll see just the range. + + // Finish writing the body: + if _, err := io.WriteString(w, contents); err != nil { + panic(err) + } + + legacyStr, err := io.ReadAll(legacy.Body) + if err != nil { + panic(err) + } + updatedStr, err := io.ReadAll(updated.Body) + if err != nil { + panic(err) + } + + if got, want := string(legacyStr), "my cached object"; got != want { + panic(fmt.Sprintf("got: %q, want: %q", got, want)) + } + if got, want := string(updatedStr), "cached"; got != want { + panic(fmt.Sprintf("got: %q, want: %q", got, want)) + } +} diff --git a/internal/abi/fastly/cache_guest.go b/internal/abi/fastly/cache_guest.go index 12ecccd..26e06f0 100644 --- a/internal/abi/fastly/cache_guest.go +++ b/internal/abi/fastly/cache_guest.go @@ -21,6 +21,14 @@ func (o *CacheLookupOptions) SetRequest(req *HTTPRequest) { o.mask |= cacheLookupOptionsMaskRequestHeaders } +func (o *CacheLookupOptions) SetAlwaysUseRequestedRange(alwaysUseRequestedRange bool) { + if alwaysUseRequestedRange { + o.mask |= cacheLookupOptionsMaskAlwaysUseRequestedRange + } else { + o.mask &= ^cacheLookupOptionsMaskAlwaysUseRequestedRange + } +} + type CacheGetBodyOptions struct { opts cacheGetBodyOptions mask cacheGetBodyOptionsMask diff --git a/internal/abi/fastly/hostcalls_noguest.go b/internal/abi/fastly/hostcalls_noguest.go index 0e1d515..8772710 100644 --- a/internal/abi/fastly/hostcalls_noguest.go +++ b/internal/abi/fastly/hostcalls_noguest.go @@ -425,6 +425,10 @@ func (o *CacheLookupOptions) SetRequest(req *HTTPRequest) error { return fmt.Errorf("not implemented") } +func (o *CacheLookupOptions) SetAlwaysUseRequestedRange(alwaysUseRequestedRange bool) error { + return fmt.Errorf("not implemented") +} + func (o *CacheGetBodyOptions) From(from uint64) error { return fmt.Errorf("not implemented") } diff --git a/internal/abi/fastly/types.go b/internal/abi/fastly/types.go index 49bbe5e..c0ae0a8 100644 --- a/internal/abi/fastly/types.go +++ b/internal/abi/fastly/types.go @@ -721,13 +721,16 @@ type cacheLookupOptions struct { // (flags (@witx repr u32) // $reserved // $request_headers +// $service_id +// $always_use_requested_range // ) // ) type cacheLookupOptionsMask prim.U32 const ( - cacheLookupOptionsMaskReserved cacheLookupOptionsMask = 0b0000_0001 // $reserved - cacheLookupOptionsMaskRequestHeaders cacheLookupOptionsMask = 0b0000_0010 // $request_headers + cacheLookupOptionsMaskReserved cacheLookupOptionsMask = 0b0000_0001 // $reserved + cacheLookupOptionsMaskRequestHeaders cacheLookupOptionsMask = 0b0000_0010 // $request_headers + cacheLookupOptionsMaskAlwaysUseRequestedRange cacheLookupOptionsMask = 0b0000_1000 // $always_use_requested_range ) // witx: