Skip to content

Commit

Permalink
Fix API to accept a context.Context
Browse files Browse the repository at this point in the history
  • Loading branch information
lestrrat committed Sep 27, 2024
1 parent 42d0429 commit e8d6641
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 25 deletions.
2 changes: 1 addition & 1 deletion client_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func ExampleClient() {
}

// Add the resource to the controller, so that it starts fetching
ctrl.AddResource(r)
ctrl.Add(ctx, r)

{
tctx, tcancel := context.WithTimeout(ctx, time.Second)
Expand Down
95 changes: 75 additions & 20 deletions controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,22 @@ import (
)

type Controller interface {
AddResource(Resource) error
Lookup(string) (Resource, error)
RemoveResource(string) error
Refresh(string) error
// Add adds a new `http.Resource` to the controller. If the resource already exists,
// it will return an error.
Add(context.Context, Resource) error

// Lookup a `httprc.Resource` by its URL. If the resource does not exist, it
// will return an error.
Lookup(context.Context, string) (Resource, error)

// Remove a `httprc.Resource` from the controller by its URL. If the resource does
// not exist, it will return an error.
Remove(context.Context, string) error

// Refresh forces a resource to be refreshed immediately. If the resource does
// not exist, or if the refresh fails, it will return an error.
Refresh(context.Context, string) error

ShutdownContext(context.Context) error
Shutdown(time.Duration) error
}
Expand Down Expand Up @@ -84,54 +96,97 @@ type lookupRequest ctrlRequest[lookupReply]
// you will either need to use the `Resource.Get()` method or use a type
// assertion to obtain a `ResourceBase[T]` to get to the actual object you are
// looking for
func (c *controller) Lookup(u string) (Resource, error) {
// to avoid having to acquire locks, we do this asynchronously
func (c *controller) Lookup(ctx context.Context, u string) (Resource, error) {
reply := make(chan lookupReply, 1)
c.incoming <- lookupRequest{
req := lookupRequest{
reply: reply,
u: u,
}
r := <-reply
return r.r, r.err
select {
case c.incoming <- req:
case <-ctx.Done():
return nil, ctx.Err()
}

select {
case <-ctx.Done():
return nil, ctx.Err()
case r := <-reply:
return r.r, r.err
}
}

// AddResource adds a new resource to the controller. If the resource already
// Add adds a new resource to the controller. If the resource already
// exists, it will return an error.
func (c *controller) AddResource(r Resource) error {
func (c *controller) Add(ctx context.Context, r Resource) error {
if !c.wl.IsAllowed(r.URL()) {
return fmt.Errorf(`httprc.Controller.AddResource: cannot add %q: %w`, r.URL(), errBlockedByWhitelist)
}

reply := make(chan error, 1)
c.incoming <- addRequest{
req := addRequest{
reply: reply,
resource: r,
}
return <-reply

select {
case <-ctx.Done():
return ctx.Err()
case c.incoming <- req:
}

select {
case <-ctx.Done():
return ctx.Err()
case err := <-reply:
return err
}
}

// RemoveResource removes a resource from the controller. If the resource does
// Remove removes a resource from the controller. If the resource does
// not exist, it will return an error.
func (c *controller) RemoveResource(u string) error {
func (c *controller) Remove(ctx context.Context, u string) error {
reply := make(chan error, 1)
c.incoming <- rmRequest{
req := rmRequest{
reply: reply,
u: u,
}
return <-reply
select {
case <-ctx.Done():
return ctx.Err()
case c.incoming <- req:
}

select {
case <-ctx.Done():
return ctx.Err()
case err := <-reply:
return err
}
}

// Refresh forces a resource to be refreshed immediately. If the resource does
// not exist, or if the refresh fails, it will return an error.
//
// This function is synchronous, and will block until the resource has been refreshed.
func (c *controller) Refresh(u string) error {
func (c *controller) Refresh(ctx context.Context, u string) error {
reply := make(chan error, 1)
c.incoming <- refreshRequest{
req := refreshRequest{
reply: reply,
u: u,
}
return <-reply
select {
case <-ctx.Done():
return ctx.Err()
case c.incoming <- req:
}

select {
case <-ctx.Done():
return ctx.Err()
case err := <-reply:
return err
}
}

func (c *controller) handleRequest(ctx context.Context, req any) {
Expand Down
8 changes: 4 additions & 4 deletions httprc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func TestClient(t *testing.T) {
r, err := tc.Create()
require.NoError(t, err, `NewResource should succeed`)

require.NoError(t, ctrl.AddResource(r), `ctrl.AddResource should succeed`)
require.NoError(t, ctrl.Add(ctx, r), `ctrl.Add should succeed`)
require.NoError(t, r.Ready(ctx), `r.Ready should succeed`)

var dst interface{}
Expand All @@ -119,7 +119,7 @@ func TestClient(t *testing.T) {

for _, tc := range testcases {
t.Run("Lookup "+tc.URL, func(t *testing.T) {
r, err := ctrl.Lookup(tc.URL)
r, err := ctrl.Lookup(ctx, tc.URL)
require.NoError(t, err, `ctrl.Lookup should succeed`)
require.Equal(t, tc.URL, r.URL(), `r.URL should return expected value`)
})
Expand Down Expand Up @@ -152,13 +152,13 @@ func TestRefresh(t *testing.T) {
r, err := httprc.NewResource[map[string]int](srv.URL, httprc.JSONTransformer[map[string]int]())
require.NoError(t, err, `NewResource should succeed`)

require.NoError(t, ctrl.AddResource(r), `ctrl.AddResource should succeed`)
require.NoError(t, ctrl.Add(ctx, r), `ctrl.Add should succeed`)

require.NoError(t, r.Ready(ctx), `r.Ready should succeed`)

for i := 1; i <= 5; i++ {
m := r.Resource()
require.Equal(t, i, m["count"], `r.Resource should return expected value`)
require.NoError(t, ctrl.Refresh(srv.URL), `r.Refresh should succeed`)
require.NoError(t, ctrl.Refresh(ctx, srv.URL), `r.Refresh should succeed`)
}
}

0 comments on commit e8d6641

Please sign in to comment.