github.com/richardwilkes/toolbox@v1.121.0/softref/softref.go (about)

     1  // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the Mozilla Public
     4  // License, version 2.0. If a copy of the MPL was not distributed with
     5  // this file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6  //
     7  // This Source Code Form is "Incompatible With Secondary Licenses", as
     8  // defined by the Mozilla Public License, version 2.0.
     9  
    10  package softref
    11  
    12  import (
    13  	"log/slog"
    14  	"runtime"
    15  	"sync"
    16  )
    17  
    18  // Pool is used to track soft references to resources.
    19  type Pool struct {
    20  	refs map[string]*softRef
    21  	lock sync.Mutex
    22  }
    23  
    24  // Resource is a resource that will be used with a pool.
    25  type Resource interface {
    26  	// Key returns a unique key for this resource. Must never change.
    27  	Key() string
    28  	// Release is called when the resource is no longer being referenced by any remaining soft references.
    29  	Release()
    30  }
    31  
    32  // SoftRef is a soft reference to a given resource.
    33  type SoftRef struct {
    34  	Resource Resource
    35  	Key      string
    36  }
    37  
    38  type softRef struct {
    39  	resource Resource
    40  	count    int
    41  }
    42  
    43  // DefaultPool is a global default soft reference pool.
    44  var DefaultPool = NewPool()
    45  
    46  // NewPool creates a new soft reference pool.
    47  func NewPool() *Pool {
    48  	return &Pool{refs: make(map[string]*softRef)}
    49  }
    50  
    51  // NewSoftRef returns a SoftRef to the given resource, along with a flag indicating if a reference existed previously.
    52  func (p *Pool) NewSoftRef(resource Resource) (ref *SoftRef, existedPreviously bool) {
    53  	key := resource.Key()
    54  	p.lock.Lock()
    55  	defer p.lock.Unlock()
    56  	r := p.refs[key]
    57  	if r != nil {
    58  		r.count++
    59  	} else {
    60  		r = &softRef{
    61  			resource: resource,
    62  			count:    1,
    63  		}
    64  		p.refs[key] = r
    65  	}
    66  	sr := &SoftRef{
    67  		Key:      key,
    68  		Resource: r.resource,
    69  	}
    70  	runtime.SetFinalizer(sr, p.finalizeSoftRef)
    71  	return sr, r.count > 1
    72  }
    73  
    74  func (p *Pool) finalizeSoftRef(ref *SoftRef) {
    75  	p.lock.Lock()
    76  	if r, ok := p.refs[ref.Key]; ok {
    77  		r.count--
    78  		if r.count == 0 {
    79  			delete(p.refs, ref.Key)
    80  			r.resource.Release()
    81  		} else if r.count < 0 {
    82  			slog.Debug("SoftRef count is invalid", "key", ref.Key, "count", r.count)
    83  		}
    84  	} else {
    85  		slog.Debug("SoftRef finalized for unknown key", "key", ref.Key)
    86  	}
    87  	p.lock.Unlock()
    88  }