| /* | |
| * Copyright 2019 gRPC 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 buffer provides an implementation of an unbounded buffer. | |
| package buffer | |
| import ( | |
| "errors" | |
| "sync" | |
| ) | |
| // Unbounded is an implementation of an unbounded buffer which does not use | |
| // extra goroutines. This is typically used for passing updates from one entity | |
| // to another within gRPC. | |
| // | |
| // To avoid extra memory allocations and type assertions, using any on | |
| // performance-critical code paths is discouraged. Use concrete types wherever | |
| // possible when instantiating Unbounded. | |
| // | |
| // All methods on this type are thread-safe and don't block on anything except | |
| // the underlying mutex used for synchronization. | |
| type Unbounded[T any] struct { | |
| c chan T | |
| closed bool | |
| closing bool | |
| mu sync.Mutex | |
| backlog []T | |
| } | |
| // NewUnbounded returns a new instance of Unbounded. | |
| func NewUnbounded[T any]() *Unbounded[T] { | |
| return &Unbounded[T]{c: make(chan T, 1)} | |
| } | |
| var errBufferClosed = errors.New("Put called on closed buffer.Unbounded") | |
| // Put adds t to the unbounded buffer. | |
| func (b *Unbounded[T]) Put(t T) error { | |
| b.mu.Lock() | |
| defer b.mu.Unlock() | |
| if b.closing { | |
| return errBufferClosed | |
| } | |
| if len(b.backlog) == 0 { | |
| select { | |
| case b.c <- t: | |
| return nil | |
| default: | |
| } | |
| } | |
| b.backlog = append(b.backlog, t) | |
| return nil | |
| } | |
| // Load sends the earliest buffered data, if any, onto the read channel returned | |
| // by Get(). Users are expected to call this every time they successfully read a | |
| // value from the read channel. | |
| func (b *Unbounded[T]) Load() { | |
| b.mu.Lock() | |
| defer b.mu.Unlock() | |
| if len(b.backlog) > 0 { | |
| select { | |
| case b.c <- b.backlog[0]: | |
| var zero T | |
| b.backlog[0] = zero | |
| b.backlog = b.backlog[1:] | |
| default: | |
| } | |
| } else if b.closing && !b.closed { | |
| b.closed = true | |
| close(b.c) | |
| } | |
| } | |
| // Get returns a read channel on which values added to the buffer, via Put(), | |
| // are sent on. | |
| // | |
| // Upon reading a value from this channel, users are expected to call Load() to | |
| // send the next buffered value onto the channel if there is any. | |
| // | |
| // If the unbounded buffer is closed, the read channel returned by this method | |
| // is closed after all data is drained. | |
| func (b *Unbounded[T]) Get() <-chan T { | |
| return b.c | |
| } | |
| // Close closes the unbounded buffer. No subsequent data may be Put(), and the | |
| // channel returned from Get() will be closed after all the data is read and | |
| // Load() is called for the final time. | |
| func (b *Unbounded[T]) Close() { | |
| b.mu.Lock() | |
| defer b.mu.Unlock() | |
| if b.closing { | |
| return | |
| } | |
| b.closing = true | |
| if len(b.backlog) == 0 { | |
| b.closed = true | |
| close(b.c) | |
| } | |
| } |