mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-19 10:02:27 -04:00
Add, GetAll and Len all check for a nil receiver and document that they do nothing, because a nil *RingLog is how callers represent a disabled log: magicsock leaves endpoint.debugUpdates nil on iOS and Android to save memory, and calls Add on it unconditionally. Clear was the one method without that check, so it would panic on those platforms. Nothing calls it on a nil log today, so this is a latent footgun rather than an observed crash, but the inconsistency is easy to fall into. Add the check and a test covering all four methods. Updates #cleanup Change-Id: Ic13693a27ad404f0e27906f4010980c9dafb7cc2 Signed-off-by: Aman Jain <jn_aman@yahoo.com>
84 lines
1.7 KiB
Go
84 lines
1.7 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package ringlog contains a limited-size concurrency-safe generic ring log.
|
|
package ringlog
|
|
|
|
import "tailscale.com/syncs"
|
|
|
|
// New creates a new [RingLog] containing at most max items.
|
|
func New[T any](max int) *RingLog[T] {
|
|
return &RingLog[T]{
|
|
max: max,
|
|
}
|
|
}
|
|
|
|
// RingLog is a concurrency-safe fixed size log window containing entries of [T].
|
|
type RingLog[T any] struct {
|
|
mu syncs.Mutex
|
|
pos int
|
|
buf []T
|
|
max int
|
|
}
|
|
|
|
// Add appends a new item to the [RingLog], possibly overwriting the oldest
|
|
// item in the log if it is already full.
|
|
//
|
|
// It does nothing if rb is nil.
|
|
func (rb *RingLog[T]) Add(t T) {
|
|
if rb == nil {
|
|
return
|
|
}
|
|
rb.mu.Lock()
|
|
defer rb.mu.Unlock()
|
|
if len(rb.buf) < rb.max {
|
|
rb.buf = append(rb.buf, t)
|
|
} else {
|
|
rb.buf[rb.pos] = t
|
|
rb.pos = (rb.pos + 1) % rb.max
|
|
}
|
|
}
|
|
|
|
// GetAll returns a copy of all the entries in the ring log in the order they
|
|
// were added.
|
|
//
|
|
// It returns nil if rb is nil.
|
|
func (rb *RingLog[T]) GetAll() []T {
|
|
if rb == nil {
|
|
return nil
|
|
}
|
|
rb.mu.Lock()
|
|
defer rb.mu.Unlock()
|
|
out := make([]T, len(rb.buf))
|
|
for i := range len(rb.buf) {
|
|
x := (rb.pos + i) % rb.max
|
|
out[i] = rb.buf[x]
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Len returns the number of elements in the ring log. Note that this value
|
|
// could change immediately after being returned if a concurrent caller
|
|
// modifies the log.
|
|
func (rb *RingLog[T]) Len() int {
|
|
if rb == nil {
|
|
return 0
|
|
}
|
|
rb.mu.Lock()
|
|
defer rb.mu.Unlock()
|
|
return len(rb.buf)
|
|
}
|
|
|
|
// Clear will empty the ring log.
|
|
//
|
|
// It does nothing if rb is nil.
|
|
func (rb *RingLog[T]) Clear() {
|
|
if rb == nil {
|
|
return
|
|
}
|
|
rb.mu.Lock()
|
|
defer rb.mu.Unlock()
|
|
rb.pos = 0
|
|
rb.buf = nil
|
|
}
|