memoise.go 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package render
  2. import (
  3. "context"
  4. "fmt"
  5. "math/rand"
  6. "sync"
  7. "github.com/bluele/gcache"
  8. "github.com/weaveworks/scope/report"
  9. )
  10. // renderCache is keyed on the combination of Memoiser and report
  11. // id. It contains promises of report.Nodes, which result from
  12. // rendering the report with the Memoiser's renderer.
  13. //
  14. // The use of promises ensures that in the absence of cache evictions
  15. // a memoiser will only ever render a report once, even when Render()
  16. // is invoked concurrently.
  17. var renderCache = gcache.New(100).LRU().Build()
  18. type memoise struct {
  19. sync.Mutex
  20. Renderer
  21. id string
  22. }
  23. // Memoise wraps the renderer in a loving embrace of caching.
  24. func Memoise(r Renderer) Renderer {
  25. if _, ok := r.(*memoise); ok {
  26. return r // fixpoint
  27. }
  28. return &memoise{
  29. Renderer: r,
  30. id: fmt.Sprintf("%x", rand.Int63()),
  31. }
  32. }
  33. // Render produces a set of Nodes given a Report. Ideally, it just
  34. // retrieves a promise from the cache and returns its value, otherwise
  35. // it stores a new promise and fulfils it by calling through to
  36. // m.Renderer.
  37. func (m *memoise) Render(ctx context.Context, rpt report.Report) Nodes {
  38. key := fmt.Sprintf("%s-%s", rpt.ID, m.id)
  39. m.Lock()
  40. v, err := renderCache.Get(key)
  41. if err == nil {
  42. m.Unlock()
  43. return v.(*promise).Get()
  44. }
  45. promise := newPromise()
  46. renderCache.Set(key, promise)
  47. m.Unlock()
  48. output := m.Renderer.Render(ctx, rpt)
  49. promise.Set(output)
  50. return output
  51. }
  52. type promise struct {
  53. val Nodes
  54. done chan struct{}
  55. }
  56. func newPromise() *promise {
  57. return &promise{done: make(chan struct{})}
  58. }
  59. func (p *promise) Set(val Nodes) {
  60. p.val = val
  61. close(p.done)
  62. }
  63. func (p *promise) Get() Nodes {
  64. <-p.done
  65. return p.val
  66. }