vegvisir/pkg/cache/memo.go

85 lines
2.2 KiB
Go
Raw Normal View History

2021-11-06 01:49:26 +01:00
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 278.
// Package memo provides a concurrency-safe non-blocking memoization
// of a function. Requests for different keys proceed in parallel.
// Concurrent requests for the same url block until the first completes.
// This implementation uses a monitor goroutine.
package cache
// Func is the type of the function to memoize.
type Func func(url, method string, route *RouteCache) (error, *ResponseCache)
// A result is the result of calling a Func.
type result struct {
value *ResponseCache
err error
}
type entry struct {
res result
ready chan struct{} // closed when res is ready
}
// A request is a message requesting that the Func be applied to url.
type request struct {
url string
method string
route *RouteCache
response chan<- result // the client wants a single result
}
type Memo struct {
requests chan request
}
// New returns a memoization of f. Clients must subsequently call Close.
func New(f Func) *Memo {
memo := &Memo{requests: make(chan request)}
go memo.server(f)
return memo
}
func (memo *Memo) Read(url, method string, route *RouteCache) (*ResponseCache, error) {
response := make(chan result)
memo.requests <- request{url, method, route, response}
res := <-response
return res.value, res.err
}
func (memo *Memo) Close() { close(memo.requests) }
func (memo *Memo) server(f Func) {
cache := make(map[string]*entry)
for req := range memo.requests {
key := req.method + "_" + req.url
e := cache[key]
if e == nil {
// This is the first request for this url.
e = &entry{ready: make(chan struct{})}
cache[key] = e
//log.Println("e.call:", req.url, req.method, req.route)
go e.call(f, req.url, req.method, req.route)
}
//log.Println("e.deliver:", req.response)
go e.deliver(req.response)
}
}
func (e *entry) call(f Func, url, method string, route *RouteCache) {
// Evaluate the function.
e.res.err, e.res.value = f(url, method, route)
// Broadcast the ready condition.
close(e.ready)
}
func (e *entry) deliver(response chan<- result) {
// Wait for the ready condition.
<-e.ready
// Send the result to the client.
response <- e.res
}