Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SuppressedLoader should allow setting group #86

Closed
markandrus opened this issue Oct 27, 2022 · 9 comments · Fixed by #89
Closed

SuppressedLoader should allow setting group #86

markandrus opened this issue Oct 27, 2022 · 9 comments · Fixed by #89
Labels

Comments

@markandrus
Copy link

SuppressedLoader looks useful! Unfortunately, group is private. So if I just create a SuppressedLoader from an existing Loader and try to use it, I get an "invalid memory address or nil pointer dereference" error.

I have had to copy SuppressedLoader into my own code where I can set group myself in order to workaround this.

Ideas

  • ttlcache should expose a method for constructing SuppressedLoaders with non-nil group?
  • SuppressedLoader should lazily set group to non-nil?
@swithek
Copy link
Contributor

swithek commented Oct 27, 2022

Yes, you are absolutely right! I'm just wondering what would be the best approach to fix this. I think to perform a lazy set of group, a new mutex would have to be introduced as well, which might degrade the performance for no real benefit.

So I'd say we have two options:

  • Expose group, as you said. There would be no breaking changes, but as a user I would expect the library to set this field itself, since there are no singleflight.Group options that I could configure.
  • Create a NewSuppressedLoader func that would set group and return a fully working loader. In this case, the Loader field could also be unexported and set inside the func (a parameter would be accepted for this), although that's a breaking change. On the other hand, since you are the first one to report this, maybe this change, albeit potentially breaking, might not affect that many users, if any at all. This way the design of the package wouldn't be negatively affected by pieces of code that would be left to be fixed in the next major release.

@swithek swithek added the bug label Oct 27, 2022
@markandrus
Copy link
Author

I think I prefer the second option; however, I'd request NewSuppressedLoader to take *singleflight.Group as an option. If the *singleflight.Group is nil, NewSuppressedLoader could create one; otherwise, it could use the provided value.

The reason I think this is desirable is because I am following your suggestion here #74 (comment). I want to be able to do

loader := NewSuppressedLoader[K, V](...)
cache.Get("key", ttlcache.WithLoader[K, V](loader))

sharing the same *singleflight.Group between SuppressedLoaders. WDYT?

@swithek
Copy link
Contributor

swithek commented Oct 27, 2022

Would the inner loader wrapped by the suppressed loader be also the same in all these places? If so, you could just reuse the same suppressed loader, e.g., attach it to the service that uses the cache:

type Service struct {
     cache *ttlcache.Cache
     loader ttlcache.Loader
}

func New() *Service {
      return Service{
           cache: ttlcache.New(),
           loader: ttlcache.NewSuppressedLoader(...),
      } 
}

func (s *Service) Get1() {
       item := s.cache.Get("key", ttlcache.WithLoader(s.loader))
}

func (s *Service) Get2() {
       item := s.cache.Get("key", ttlcache.WithLoader(s.loader))
}

@markandrus
Copy link
Author

markandrus commented Oct 28, 2022

I do something like this, so I don't believe I can share the loader:

type Service struct {
	cache *ttlcache.Cache
	group *singleflight.Group
}

func (s *Service) Get(ctx context.Context, key K) V {
	var loaded V

	loader := func(cache *ttlcache.Cache, key K) *ttlcache.Item {
		// 1. Make an API call, passing ctx.
		// 2. Set loaded to the API result.
		// 3. Update the cache if the API result can be cached; otherwise, return nil.
	}

	loaderFunc := ttlcache.LoaderFunc(loader)

	suppressedLoader := SuppressedLoader{
		Loader: loaderFunc,
		group:  group,
	}

	if cached := s.cache.Get(key, ttlcache.WithLoader(suppressedLoader)); cached != nil {
		return cached.Value()
	}

	return loaded
}

@swithek
Copy link
Contributor

swithek commented Oct 28, 2022

I see. In that case, the required changes are as follows:

  • Transform the embedded Loader into an unexported loader Loader field.
  • Create a NewSuppressedLoader(loader Loader, group *singleflight.Group) func. If group is nil, it is automatically created:
func NewSuppressedLoader(loader Loader, group *singleflight.Group) *SuppressedLoader {
    if group == nil {
        group = &singleflight.Group{}
    }
    
    return &SuppressedLoader{
        loader: loader,
        group: group,
    }
}

Have I missed anything?

@markandrus
Copy link
Author

No, that sounds great! 🙌🏼

davseby added a commit that referenced this issue Nov 4, 2022
Currently there is no way to use the suppressed loader, as the group attribute is always nil (issue #86). This adds a new function which initializes the structure with the Loader and group parameters, if the group parameter is nil, it creates a new single flight group object.
@austinbyers
Copy link

I just ran into this same issue! I love the SuppressedLoader, but it's not in the latest release. Any chance we can get a new release to include this fix?

@swithek
Copy link
Contributor

swithek commented Dec 31, 2022

Yep, you can find it here. Sorry for the delay.

@austinbyers
Copy link

Awesome, thanks for the quick response!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants