85 lines
2.2 KiB
Go
85 lines
2.2 KiB
Go
|
// 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
|
||
|
}
|