hugo/identity/identity.go
Bjørn Erik Pedersen 4ef9baf5bd Only invoke a given cached partial once
Note that this is backed by a LRU cache (which we soon shall see more usage of), so if you're a heavy user of cached partials it may be evicted and
refreshed if needed. But in most cases every partial is only invoked once.

This commit also adds a timeout (the global `timeout` config option) to make infinite recursion in partials
easier to reason about.

```
name              old time/op    new time/op    delta
IncludeCached-10    8.92ms ± 0%    8.48ms ± 1%   -4.87%  (p=0.016 n=4+5)

name              old alloc/op   new alloc/op   delta
IncludeCached-10    6.65MB ± 0%    5.17MB ± 0%  -22.32%  (p=0.002 n=6+6)

name              old allocs/op  new allocs/op  delta
IncludeCached-10      117k ± 0%       71k ± 0%  -39.44%  (p=0.002 n=6+6)
```

Closes #4086
Updates #9588
2023-01-25 17:35:23 +01:00

178 lines
4 KiB
Go

// Copyright 2023 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package identity
import (
"path/filepath"
"strings"
"sync"
"sync/atomic"
)
// NewIdentityManager creates a new Manager starting at id.
func NewManager(id Provider) Manager {
return &identityManager{
Provider: id,
ids: Identities{id.GetIdentity(): id},
}
}
// NewPathIdentity creates a new Identity with the two identifiers
// type and path.
func NewPathIdentity(typ, pat string) PathIdentity {
pat = strings.ToLower(strings.TrimPrefix(filepath.ToSlash(pat), "/"))
return PathIdentity{Type: typ, Path: pat}
}
// Identities stores identity providers.
type Identities map[Identity]Provider
func (ids Identities) search(depth int, id Identity) Provider {
if v, found := ids[id.GetIdentity()]; found {
return v
}
depth++
// There may be infinite recursion in templates.
if depth > 100 {
// Bail out.
return nil
}
for _, v := range ids {
switch t := v.(type) {
case IdentitiesProvider:
if nested := t.GetIdentities().search(depth, id); nested != nil {
return nested
}
}
}
return nil
}
// IdentitiesProvider provides all Identities.
type IdentitiesProvider interface {
GetIdentities() Identities
}
// Identity represents an thing that can provide an identify. This can be
// any Go type, but the Identity returned by GetIdentify must be hashable.
type Identity interface {
Provider
Name() string
}
// Manager manages identities, and is itself a Provider of Identity.
type Manager interface {
SearchProvider
Add(ids ...Provider)
Reset()
}
// SearchProvider provides access to the chained set of identities.
type SearchProvider interface {
Provider
IdentitiesProvider
Search(id Identity) Provider
}
// A PathIdentity is a common identity identified by a type and a path, e.g. "layouts" and "_default/single.html".
type PathIdentity struct {
Type string
Path string
}
// GetIdentity returns itself.
func (id PathIdentity) GetIdentity() Identity {
return id
}
// Name returns the Path.
func (id PathIdentity) Name() string {
return id.Path
}
// A KeyValueIdentity a general purpose identity.
type KeyValueIdentity struct {
Key string
Value string
}
// GetIdentity returns itself.
func (id KeyValueIdentity) GetIdentity() Identity {
return id
}
// Name returns the Key.
func (id KeyValueIdentity) Name() string {
return id.Key
}
// Provider provides the comparable Identity.
type Provider interface {
// GetIdentity is for internal use.
GetIdentity() Identity
}
type identityManager struct {
sync.Mutex
Provider
ids Identities
}
func (im *identityManager) Add(ids ...Provider) {
im.Lock()
for _, id := range ids {
im.ids[id.GetIdentity()] = id
}
im.Unlock()
}
func (im *identityManager) Reset() {
im.Lock()
id := im.GetIdentity()
im.ids = Identities{id.GetIdentity(): id}
im.Unlock()
}
// TODO(bep) these identities are currently only read on server reloads
// so there should be no concurrency issues, but that may change.
func (im *identityManager) GetIdentities() Identities {
im.Lock()
defer im.Unlock()
return im.ids
}
func (im *identityManager) Search(id Identity) Provider {
im.Lock()
defer im.Unlock()
return im.ids.search(0, id.GetIdentity())
}
// Incrementer increments and returns the value.
// Typically used for IDs.
type Incrementer interface {
Incr() int
}
// IncrementByOne implements Incrementer adding 1 every time Incr is called.
type IncrementByOne struct {
counter uint64
}
func (c *IncrementByOne) Incr() int {
return int(atomic.AddUint64(&c.counter, uint64(1)))
}