// 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 }