diff --git a/go.mod b/go.mod index 0827c9cc8..3f33a468b 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( k8s.io/client-go v0.29.6 k8s.io/code-generator v0.29.7 k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 - k8s.io/utils v0.0.0-20240102154912-e7106e64919e + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 knative.dev/pkg v0.0.0-20240416145024-0f34a8815650 ) diff --git a/go.sum b/go.sum index d6a2ef92d..83f5bce52 100644 --- a/go.sum +++ b/go.sum @@ -1549,8 +1549,8 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/pkg v0.0.0-20240416145024-0f34a8815650 h1:m2ahFUO0L2VrgGDYdyOUFdE6xBd3pLXAJozLJwqLRQM= knative.dev/pkg v0.0.0-20240416145024-0f34a8815650/go.mod h1:soFw5ss08G4PU3JiFDKqiZRd2U7xoqcfNpJP1coIXkY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/vendor/k8s.io/utils/buffer/ring_growing.go b/vendor/k8s.io/utils/buffer/ring_growing.go index 86965a513..0f6d31d3e 100644 --- a/vendor/k8s.io/utils/buffer/ring_growing.go +++ b/vendor/k8s.io/utils/buffer/ring_growing.go @@ -16,31 +16,57 @@ limitations under the License. package buffer +// defaultRingSize defines the default ring size if not specified +const defaultRingSize = 16 + +// RingGrowingOptions sets parameters for [RingGrowing] and +// [TypedRingGrowing]. +type RingGrowingOptions struct { + // InitialSize is the number of pre-allocated elements in the + // initial underlying storage buffer. + InitialSize int +} + // RingGrowing is a growing ring buffer. // Not thread safe. -type RingGrowing struct { - data []interface{} +// +// Deprecated: Use TypedRingGrowing[any] instead. +type RingGrowing = TypedRingGrowing[any] + +// NewRingGrowing constructs a new RingGrowing instance with provided parameters. +// +// Deprecated: Use NewTypedRingGrowing[any] instead. +func NewRingGrowing(initialSize int) *RingGrowing { + return NewTypedRingGrowing[any](RingGrowingOptions{InitialSize: initialSize}) +} + +// TypedRingGrowing is a growing ring buffer. +// The zero value has an initial size of 0 and is ready to use. +// Not thread safe. +type TypedRingGrowing[T any] struct { + data []T n int // Size of Data beg int // First available element readable int // Number of data items available } -// NewRingGrowing constructs a new RingGrowing instance with provided parameters. -func NewRingGrowing(initialSize int) *RingGrowing { - return &RingGrowing{ - data: make([]interface{}, initialSize), - n: initialSize, +// NewTypedRingGrowing constructs a new TypedRingGrowing instance with provided parameters. +func NewTypedRingGrowing[T any](opts RingGrowingOptions) *TypedRingGrowing[T] { + return &TypedRingGrowing[T]{ + data: make([]T, opts.InitialSize), + n: opts.InitialSize, } } // ReadOne reads (consumes) first item from the buffer if it is available, otherwise returns false. -func (r *RingGrowing) ReadOne() (data interface{}, ok bool) { +func (r *TypedRingGrowing[T]) ReadOne() (data T, ok bool) { if r.readable == 0 { - return nil, false + return } r.readable-- element := r.data[r.beg] - r.data[r.beg] = nil // Remove reference to the object to help GC + var zero T + r.data[r.beg] = zero // Remove reference to the object to help GC if r.beg == r.n-1 { // Was the last element r.beg = 0 @@ -51,11 +77,14 @@ func (r *RingGrowing) ReadOne() (data interface{}, ok bool) { } // WriteOne adds an item to the end of the buffer, growing it if it is full. -func (r *RingGrowing) WriteOne(data interface{}) { +func (r *TypedRingGrowing[T]) WriteOne(data T) { if r.readable == r.n { // Time to grow newN := r.n * 2 - newData := make([]interface{}, newN) + if newN == 0 { + newN = defaultRingSize + } + newData := make([]T, newN) to := r.beg + r.readable if to <= r.n { copy(newData, r.data[r.beg:to]) @@ -70,3 +99,72 @@ func (r *RingGrowing) WriteOne(data interface{}) { r.data[(r.readable+r.beg)%r.n] = data r.readable++ } + +// Len returns the number of items in the buffer. +func (r *TypedRingGrowing[T]) Len() int { + return r.readable +} + +// Cap returns the capacity of the buffer. +func (r *TypedRingGrowing[T]) Cap() int { + return r.n +} + +// RingOptions sets parameters for [Ring]. +type RingOptions struct { + // InitialSize is the number of pre-allocated elements in the + // initial underlying storage buffer. + InitialSize int + // NormalSize is the number of elements to allocate for new storage + // buffers once the Ring is consumed and + // can shrink again. + NormalSize int +} + +// Ring is a dynamically-sized ring buffer which can grow and shrink as-needed. +// The zero value has an initial size and normal size of 0 and is ready to use. +// Not thread safe. +type Ring[T any] struct { + growing TypedRingGrowing[T] + normalSize int // Limits the size of the buffer that is kept for reuse. Read-only. +} + +// NewRing constructs a new Ring instance with provided parameters. +func NewRing[T any](opts RingOptions) *Ring[T] { + return &Ring[T]{ + growing: *NewTypedRingGrowing[T](RingGrowingOptions{InitialSize: opts.InitialSize}), + normalSize: opts.NormalSize, + } +} + +// ReadOne reads (consumes) first item from the buffer if it is available, +// otherwise returns false. When the buffer has been totally consumed and has +// grown in size beyond its normal size, it shrinks down to its normal size again. +func (r *Ring[T]) ReadOne() (data T, ok bool) { + element, ok := r.growing.ReadOne() + + if r.growing.readable == 0 && r.growing.n > r.normalSize { + // The buffer is empty. Reallocate a new buffer so the old one can be + // garbage collected. + r.growing.data = make([]T, r.normalSize) + r.growing.n = r.normalSize + r.growing.beg = 0 + } + + return element, ok +} + +// WriteOne adds an item to the end of the buffer, growing it if it is full. +func (r *Ring[T]) WriteOne(data T) { + r.growing.WriteOne(data) +} + +// Len returns the number of items in the buffer. +func (r *Ring[T]) Len() int { + return r.growing.Len() +} + +// Cap returns the capacity of the buffer. +func (r *Ring[T]) Cap() int { + return r.growing.Cap() +} diff --git a/vendor/k8s.io/utils/clock/testing/fake_clock.go b/vendor/k8s.io/utils/clock/testing/fake_clock.go index 79e11deb6..9503690be 100644 --- a/vendor/k8s.io/utils/clock/testing/fake_clock.go +++ b/vendor/k8s.io/utils/clock/testing/fake_clock.go @@ -48,7 +48,6 @@ type fakeClockWaiter struct { stepInterval time.Duration skipIfBlocked bool destChan chan time.Time - fired bool afterFunc func() } @@ -198,12 +197,10 @@ func (f *FakeClock) setTimeLocked(t time.Time) { if w.skipIfBlocked { select { case w.destChan <- t: - w.fired = true default: } } else { w.destChan <- t - w.fired = true } if w.afterFunc != nil { @@ -224,14 +221,26 @@ func (f *FakeClock) setTimeLocked(t time.Time) { f.waiters = newWaiters } -// HasWaiters returns true if After or AfterFunc has been called on f but not yet satisfied (so you can -// write race-free tests). +// HasWaiters returns true if Waiters() returns non-0 (so you can write race-free tests). func (f *FakeClock) HasWaiters() bool { f.lock.RLock() defer f.lock.RUnlock() return len(f.waiters) > 0 } +// Waiters returns the number of "waiters" on the clock (so you can write race-free +// tests). A waiter exists for: +// - every call to After that has not yet signaled its channel. +// - every call to AfterFunc that has not yet called its callback. +// - every timer created with NewTimer which is currently ticking. +// - every ticker created with NewTicker which is currently ticking. +// - every ticker created with Tick. +func (f *FakeClock) Waiters() int { + f.lock.RLock() + defer f.lock.RUnlock() + return len(f.waiters) +} + // Sleep is akin to time.Sleep func (f *FakeClock) Sleep(d time.Duration) { f.Step(d) @@ -305,44 +314,48 @@ func (f *fakeTimer) C() <-chan time.Time { return f.waiter.destChan } -// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise. +// Stop prevents the Timer from firing. It returns true if the call stops the +// timer, false if the timer has already expired or been stopped. func (f *fakeTimer) Stop() bool { f.fakeClock.lock.Lock() defer f.fakeClock.lock.Unlock() + active := false newWaiters := make([]*fakeClockWaiter, 0, len(f.fakeClock.waiters)) for i := range f.fakeClock.waiters { w := f.fakeClock.waiters[i] if w != &f.waiter { newWaiters = append(newWaiters, w) + continue } + // If timer is found, it has not been fired yet. + active = true } f.fakeClock.waiters = newWaiters - return !f.waiter.fired + return active } -// Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet -// fired, or false otherwise. +// Reset changes the timer to expire after duration d. It returns true if the +// timer had been active, false if the timer had expired or been stopped. func (f *fakeTimer) Reset(d time.Duration) bool { f.fakeClock.lock.Lock() defer f.fakeClock.lock.Unlock() - active := !f.waiter.fired + active := false - f.waiter.fired = false f.waiter.targetTime = f.fakeClock.time.Add(d) - var isWaiting bool for i := range f.fakeClock.waiters { w := f.fakeClock.waiters[i] if w == &f.waiter { - isWaiting = true + // If timer is found, it has not been fired yet. + active = true break } } - if !isWaiting { + if !active { f.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter) } diff --git a/vendor/k8s.io/utils/integer/integer.go b/vendor/k8s.io/utils/integer/integer.go index e0811e834..f64d64955 100644 --- a/vendor/k8s.io/utils/integer/integer.go +++ b/vendor/k8s.io/utils/integer/integer.go @@ -18,7 +18,8 @@ package integer import "math" -// IntMax returns the maximum of the params +// IntMax returns the maximum of the params. +// Deprecated: for new code, use the max() builtin instead. func IntMax(a, b int) int { if b > a { return b @@ -26,7 +27,8 @@ func IntMax(a, b int) int { return a } -// IntMin returns the minimum of the params +// IntMin returns the minimum of the params. +// Deprecated: for new code, use the min() builtin instead. func IntMin(a, b int) int { if b < a { return b @@ -34,7 +36,8 @@ func IntMin(a, b int) int { return a } -// Int32Max returns the maximum of the params +// Int32Max returns the maximum of the params. +// Deprecated: for new code, use the max() builtin instead. func Int32Max(a, b int32) int32 { if b > a { return b @@ -42,7 +45,8 @@ func Int32Max(a, b int32) int32 { return a } -// Int32Min returns the minimum of the params +// Int32Min returns the minimum of the params. +// Deprecated: for new code, use the min() builtin instead. func Int32Min(a, b int32) int32 { if b < a { return b @@ -50,7 +54,8 @@ func Int32Min(a, b int32) int32 { return a } -// Int64Max returns the maximum of the params +// Int64Max returns the maximum of the params. +// Deprecated: for new code, use the max() builtin instead. func Int64Max(a, b int64) int64 { if b > a { return b @@ -58,7 +63,8 @@ func Int64Max(a, b int64) int64 { return a } -// Int64Min returns the minimum of the params +// Int64Min returns the minimum of the params. +// Deprecated: for new code, use the min() builtin instead. func Int64Min(a, b int64) int64 { if b < a { return b diff --git a/vendor/k8s.io/utils/net/multi_listen.go b/vendor/k8s.io/utils/net/multi_listen.go new file mode 100644 index 000000000..7cb7795be --- /dev/null +++ b/vendor/k8s.io/utils/net/multi_listen.go @@ -0,0 +1,195 @@ +/* +Copyright 2024 The Kubernetes Authors. + +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 net + +import ( + "context" + "fmt" + "net" + "sync" +) + +// connErrPair pairs conn and error which is returned by accept on sub-listeners. +type connErrPair struct { + conn net.Conn + err error +} + +// multiListener implements net.Listener +type multiListener struct { + listeners []net.Listener + wg sync.WaitGroup + + // connCh passes accepted connections, from child listeners to parent. + connCh chan connErrPair + // stopCh communicates from parent to child listeners. + stopCh chan struct{} +} + +// compile time check to ensure *multiListener implements net.Listener +var _ net.Listener = &multiListener{} + +// MultiListen returns net.Listener which can listen on and accept connections for +// the given network on multiple addresses. Internally it uses stdlib to create +// sub-listener and multiplexes connection requests using go-routines. +// The network must be "tcp", "tcp4" or "tcp6". +// It follows the semantics of net.Listen that primarily means: +// 1. If the host is an unspecified/zero IP address with "tcp" network, MultiListen +// listens on all available unicast and anycast IP addresses of the local system. +// 2. Use "tcp4" or "tcp6" to exclusively listen on IPv4 or IPv6 family, respectively. +// 3. The host can accept names (e.g, localhost) and it will create a listener for at +// most one of the host's IP. +func MultiListen(ctx context.Context, network string, addrs ...string) (net.Listener, error) { + var lc net.ListenConfig + return multiListen( + ctx, + network, + addrs, + func(ctx context.Context, network, address string) (net.Listener, error) { + return lc.Listen(ctx, network, address) + }) +} + +// multiListen implements MultiListen by consuming stdlib functions as dependency allowing +// mocking for unit-testing. +func multiListen( + ctx context.Context, + network string, + addrs []string, + listenFunc func(ctx context.Context, network, address string) (net.Listener, error), +) (net.Listener, error) { + if !(network == "tcp" || network == "tcp4" || network == "tcp6") { + return nil, fmt.Errorf("network %q not supported", network) + } + if len(addrs) == 0 { + return nil, fmt.Errorf("no address provided to listen on") + } + + ml := &multiListener{ + connCh: make(chan connErrPair), + stopCh: make(chan struct{}), + } + for _, addr := range addrs { + l, err := listenFunc(ctx, network, addr) + if err != nil { + // close all the sub-listeners and exit + _ = ml.Close() + return nil, err + } + ml.listeners = append(ml.listeners, l) + } + + for _, l := range ml.listeners { + ml.wg.Add(1) + go func(l net.Listener) { + defer ml.wg.Done() + for { + // Accept() is blocking, unless ml.Close() is called, in which + // case it will return immediately with an error. + conn, err := l.Accept() + // This assumes that ANY error from Accept() will terminate the + // sub-listener. We could maybe be more precise, but it + // doesn't seem necessary. + terminate := err != nil + + select { + case ml.connCh <- connErrPair{conn: conn, err: err}: + case <-ml.stopCh: + // In case we accepted a connection AND were stopped, and + // this select-case was chosen, just throw away the + // connection. This avoids potentially blocking on connCh + // or leaking a connection. + if conn != nil { + _ = conn.Close() + } + terminate = true + } + // Make sure we don't loop on Accept() returning an error and + // the select choosing the channel case. + if terminate { + return + } + } + }(l) + } + return ml, nil +} + +// Accept implements net.Listener. It waits for and returns a connection from +// any of the sub-listener. +func (ml *multiListener) Accept() (net.Conn, error) { + // wait for any sub-listener to enqueue an accepted connection + connErr, ok := <-ml.connCh + if !ok { + // The channel will be closed only when Close() is called on the + // multiListener. Closing of this channel implies that all + // sub-listeners are also closed, which causes a "use of closed + // network connection" error on their Accept() calls. We return the + // same error for multiListener.Accept() if multiListener.Close() + // has already been called. + return nil, fmt.Errorf("use of closed network connection") + } + return connErr.conn, connErr.err +} + +// Close implements net.Listener. It will close all sub-listeners and wait for +// the go-routines to exit. +func (ml *multiListener) Close() error { + // Make sure this can be called repeatedly without explosions. + select { + case <-ml.stopCh: + return fmt.Errorf("use of closed network connection") + default: + } + + // Tell all sub-listeners to stop. + close(ml.stopCh) + + // Closing the listeners causes Accept() to immediately return an error in + // the sub-listener go-routines. + for _, l := range ml.listeners { + _ = l.Close() + } + + // Wait for all the sub-listener go-routines to exit. + ml.wg.Wait() + close(ml.connCh) + + // Drain any already-queued connections. + for connErr := range ml.connCh { + if connErr.conn != nil { + _ = connErr.conn.Close() + } + } + return nil +} + +// Addr is an implementation of the net.Listener interface. It always returns +// the address of the first listener. Callers should use conn.LocalAddr() to +// obtain the actual local address of the sub-listener. +func (ml *multiListener) Addr() net.Addr { + return ml.listeners[0].Addr() +} + +// Addrs is like Addr, but returns the address for all registered listeners. +func (ml *multiListener) Addrs() []net.Addr { + var ret []net.Addr + for _, l := range ml.listeners { + ret = append(ret, l.Addr()) + } + return ret +} diff --git a/vendor/k8s.io/utils/trace/trace.go b/vendor/k8s.io/utils/trace/trace.go index 187eb5d8c..559aebb59 100644 --- a/vendor/k8s.io/utils/trace/trace.go +++ b/vendor/k8s.io/utils/trace/trace.go @@ -192,7 +192,7 @@ func (t *Trace) Log() { t.endTime = &endTime t.lock.Unlock() // an explicit logging request should dump all the steps out at the higher level - if t.parentTrace == nil { // We don't start logging until Log or LogIfLong is called on the root trace + if t.parentTrace == nil && klogV(2) { // We don't start logging until Log or LogIfLong is called on the root trace t.logTrace() } } diff --git a/vendor/modules.txt b/vendor/modules.txt index ddf50611e..f9c39ca75 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1065,7 +1065,7 @@ k8s.io/kube-openapi/pkg/spec3 k8s.io/kube-openapi/pkg/util/proto k8s.io/kube-openapi/pkg/util/sets k8s.io/kube-openapi/pkg/validation/spec -# k8s.io/utils v0.0.0-20240102154912-e7106e64919e +# k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 ## explicit; go 1.18 k8s.io/utils/buffer k8s.io/utils/clock