mirror of
https://github.com/mailgun/groupcache.git
synced 2024-11-16 14:10:04 +00:00
Compare commits
2 Commits
8eb6132379
...
a8d6943162
Author | SHA1 | Date | |
---|---|---|---|
|
a8d6943162 | ||
|
9f417fbc4f |
@ -4,7 +4,9 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
#
|
# This change log is deprecated in favor of github release functionality.
|
||||||
|
# See https://github.com/mailgun/groupcache/releases for recent change activity.
|
||||||
|
|
||||||
## [2.3.1] - 2022-05-17
|
## [2.3.1] - 2022-05-17
|
||||||
### Changed
|
### Changed
|
||||||
* Fix example in README #40
|
* Fix example in README #40
|
||||||
|
33
errors.go
Normal file
33
errors.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package groupcache
|
||||||
|
|
||||||
|
// ErrNotFound should be returned from an implementation of `GetterFunc` to indicate the
|
||||||
|
// requested value is not available. When remote HTTP calls are made to retrieve values from
|
||||||
|
// other groupcache instances, returning this error will indicate to groupcache that the
|
||||||
|
// value requested is not available, and it should NOT attempt to call `GetterFunc` locally.
|
||||||
|
type ErrNotFound struct {
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrNotFound) Error() string {
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrNotFound) Is(target error) bool {
|
||||||
|
_, ok := target.(*ErrNotFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRemoteCall is returned from `group.Get()` when an error that is not a `ErrNotFound`
|
||||||
|
// is returned during a remote HTTP instance call
|
||||||
|
type ErrRemoteCall struct {
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrRemoteCall) Error() string {
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ErrRemoteCall) Is(target error) bool {
|
||||||
|
_, ok := target.(*ErrRemoteCall)
|
||||||
|
return ok
|
||||||
|
}
|
@ -427,8 +427,17 @@ func (g *Group) load(ctx context.Context, key string, dest Sink) (value ByteView
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
g.Stats.PeerLoads.Add(1)
|
g.Stats.PeerLoads.Add(1)
|
||||||
return value, nil
|
return value, nil
|
||||||
} else if errors.Is(err, context.Canceled) {
|
}
|
||||||
// do not count context cancellation as a peer error
|
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, &ErrNotFound{}) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, &ErrRemoteCall{}) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,10 +456,6 @@ func (g *Group) load(ctx context.Context, key string, dest Sink) (value ByteView
|
|||||||
// since the context is no longer valid
|
// since the context is no longer valid
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO(bradfitz): log the peer's error? keep
|
|
||||||
// log of the past few for /groupcachez? It's
|
|
||||||
// probably boring (normal task movement), so not
|
|
||||||
// worth logging I imagine.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err = g.getLocally(ctx, key, dest)
|
value, err = g.getLocally(ctx, key, dest)
|
||||||
|
19
http.go
19
http.go
@ -19,6 +19,7 @@ package groupcache
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -238,7 +239,11 @@ func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
value := AllocatingByteSliceSink(&b)
|
value := AllocatingByteSliceSink(&b)
|
||||||
err := group.Get(ctx, key, value)
|
err := group.Get(ctx, key, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
if errors.Is(err, &ErrNotFound{}) {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +326,17 @@ func (h *httpGetter) Get(ctx context.Context, in *pb.GetRequest, out *pb.GetResp
|
|||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
msg, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1024*1024)) // Limit reading the error body to max 1 MiB
|
// Limit reading the error body to max 1 MiB
|
||||||
|
msg, _ := io.ReadAll(io.LimitReader(res.Body, 1024*1024))
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusNotFound {
|
||||||
|
return &ErrNotFound{Msg: strings.Trim(string(msg), "\n")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.StatusCode == http.StatusServiceUnavailable {
|
||||||
|
return &ErrRemoteCall{Msg: strings.Trim(string(msg), "\n")}
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Errorf("server returned: %v, %v", res.Status, string(msg))
|
return fmt.Errorf("server returned: %v, %v", res.Status, string(msg))
|
||||||
}
|
}
|
||||||
b := bufferPool.Get().(*bytes.Buffer)
|
b := bufferPool.Get().(*bytes.Buffer)
|
||||||
|
28
http_test.go
28
http_test.go
@ -33,6 +33,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -183,6 +185,24 @@ func TestHTTPPool(t *testing.T) {
|
|||||||
if suffix := ":" + key; !strings.HasSuffix(value, suffix) {
|
if suffix := ":" + key; !strings.HasSuffix(value, suffix) {
|
||||||
t.Errorf("Get(%q) = %q, want value ending in %q", key, value, suffix)
|
t.Errorf("Get(%q) = %q, want value ending in %q", key, value, suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a key that does not exist
|
||||||
|
err := g.Get(ctx, "IDoNotExist", StringSink(&value))
|
||||||
|
errNotFound := &ErrNotFound{}
|
||||||
|
if !errors.As(err, &errNotFound) {
|
||||||
|
t.Fatal(errors.New("expected error to be 'ErrNotFound'"))
|
||||||
|
}
|
||||||
|
assert.Equal(t, "I do not exist error", errNotFound.Error())
|
||||||
|
|
||||||
|
// Get a key that is guaranteed to return an error
|
||||||
|
err = g.Get(ctx, "IAlwaysReturnAnError", StringSink(&value))
|
||||||
|
errRemoteCall := &ErrRemoteCall{}
|
||||||
|
if !errors.As(err, &errRemoteCall) {
|
||||||
|
t.Fatal(errors.New("expected error to be 'ErrRemoteCall'"))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "I am a GetterFunc error", errRemoteCall.Error())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testKeys(n int) (keys []string) {
|
func testKeys(n int) (keys []string) {
|
||||||
@ -200,6 +220,14 @@ func beChildForTestHTTPPool(t *testing.T) {
|
|||||||
p.Set(addrToURL(addrs)...)
|
p.Set(addrToURL(addrs)...)
|
||||||
|
|
||||||
getter := GetterFunc(func(ctx context.Context, key string, dest Sink) error {
|
getter := GetterFunc(func(ctx context.Context, key string, dest Sink) error {
|
||||||
|
if key == "IDoNotExist" {
|
||||||
|
return &ErrNotFound{Msg: "I do not exist error"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "IAlwaysReturnAnError" {
|
||||||
|
return &ErrRemoteCall{Msg: "I am a GetterFunc error"}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := http.Get(*serverAddr); err != nil {
|
if _, err := http.Get(*serverAddr); err != nil {
|
||||||
t.Logf("HTTP request from getter failed with '%s'", err)
|
t.Logf("HTTP request from getter failed with '%s'", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user