Files
Shailend ChandandgVisor bot f739fd243b Rename unix transport identifiers to demystify the package
Three nearly identical names covered three unrelated concepts
(ConnectedEndpoint, connectedEndpoint, connectionedEndpoint), and the
notification callbacks were named for the opposite of what they do
(RecvNotify woke writers, SendNotify woke readers).

- ConnectedEndpoint -> Sender, the write half of a connection
  (implementations queueSender, HostSender, SCMSender); the
  baseEndpoint field becomes connected -> peer: the type is named for
  what you do with it, the field for its role to the holder.
- SendNotify -> NotifyDataReady, RecvNotify -> NotifyWriteSpace,
  CloseNotify -> NotifyStateChange; each named for the wakeup it
  delivers. NotifyStateChange must wake hangup-only pollers.
- queue's embedded waiter queues become readerWaiters/writerWaiters,
  and baseEndpoint's embedded *waiter.Queue becomes a named field,
  WaitQueue, so wakeup call sites name their target.
- writeShutdown and wrShutdown/rdShutdown ->
  sendShutdown/recvShutdown, the shutdown(2) directions they track.
- A package doc block describes how the pieces fit together.

No behavior change.

PiperOrigin-RevId: 966732452
2026-08-18 12:14:22 -07:00

284 lines
7.5 KiB
Go

// Copyright 2018 The gVisor 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 transport
import (
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/syserr"
"gvisor.dev/gvisor/pkg/waiter"
)
// queue is a buffer queue.
//
// +stateify savable
type queue struct {
queueRefs
// readerWaiters and writerWaiters route this queue's wakeups; nothing
// registers here directly. They alias the WaitQueues of the endpoints
// this queue affects: gaining data or closing wakes readerWaiters (the
// endpoint that reads from this queue), freeing space wakes
// writerWaiters (the endpoint that writes into it — or an unwatched
// placeholder on connectionless sockets, which have no fixed writer).
readerWaiters *waiter.Queue
writerWaiters *waiter.Queue
mu queueMutex `state:"nosave"`
closed atomicbitops.Bool
unread bool
used int64
limit int64
dataList messageList
}
// Close closes q for reading and writing. It is immediately not writable and
// will become unreadable when no more data is pending.
//
// Both the read and write queues must be notified after closing:
// q.readerWaiters.Notify(waiter.ReadableEvents)
// q.writerWaiters.Notify(waiter.WritableEvents)
func (q *queue) Close() {
q.mu.Lock()
q.closed.Store(true)
q.mu.Unlock()
}
func (q *queue) isClosed() bool {
return q.closed.Load()
}
// Reset empties the queue and Releases all of the Entries.
//
// Both the read and write queues must be notified after resetting:
// q.readerWaiters.Notify(waiter.ReadableEvents)
// q.writerWaiters.Notify(waiter.WritableEvents)
func (q *queue) Reset(ctx context.Context) {
q.mu.Lock()
dataList := q.dataList
q.dataList.Reset()
q.used = 0
q.mu.Unlock()
for cur := dataList.Front(); cur != nil; cur = cur.Next() {
cur.Release(ctx)
}
}
// DecRef implements RefCounter.DecRef.
func (q *queue) DecRef(ctx context.Context) {
q.queueRefs.DecRef(func() {
// We don't need to notify after resetting because no one cares about
// this queue after all references have been dropped.
q.Reset(ctx)
})
}
// IsReadable determines if q is currently readable.
func (q *queue) IsReadable() bool {
q.mu.Lock()
defer q.mu.Unlock()
return q.closed.RacyLoad() || q.dataList.Front() != nil
}
// bufWritable returns true if there is space for writing.
//
// N.B. Linux only considers a unix socket "writable" if >75% of the buffer is
// free.
//
// See net/unix/af_unix.c:unix_writeable.
func (q *queue) bufWritable() bool {
return 4*q.used < q.limit
}
// IsWritable determines if q is currently writable.
func (q *queue) IsWritable() bool {
q.mu.Lock()
defer q.mu.Unlock()
return q.closed.RacyLoad() || q.bufWritable()
}
// Enqueue adds an entry to the data queue if room is available.
//
// If discardEmpty is true and there are zero bytes of data, the packet is
// dropped.
//
// If truncate is true, Enqueue may truncate the message before enqueuing it.
// Otherwise, the entire message must fit. If l is less than the size of data,
// err indicates why.
//
// Ownership of c.Rights transfers to the queue if and only if the message is
// consumed: either enqueued, which includes a truncated write returning a
// non-zero l along with syserr.ErrWouldBlock, or dropped on account of
// discardEmpty, in which case the rights are released here. Whenever Enqueue
// instead bails out early with an error and l == 0, the caller retains
// ownership of c.Rights and remains responsible for releasing them.
//
// If notify is true, readerWaiters.Notify must be called:
// q.readerWaiters.Notify(waiter.ReadableEvents)
func (q *queue) Enqueue(ctx context.Context, data [][]byte, c ControlMessages, from Address, discardEmpty bool, truncate bool) (l int64, notify bool, err *syserr.Error) {
q.mu.Lock()
if q.closed.RacyLoad() {
q.mu.Unlock()
return 0, false, syserr.ErrClosedForSend
}
for _, d := range data {
l += int64(len(d))
}
if discardEmpty && l == 0 {
q.mu.Unlock()
c.Release(ctx)
return 0, false, nil
}
free := q.limit - q.used
if l > free && truncate {
if free <= 0 {
// Message can't fit right now.
q.mu.Unlock()
return 0, false, syserr.ErrWouldBlock
}
l = free
err = syserr.ErrWouldBlock
}
if l > q.limit {
// Message is too big to ever fit.
q.mu.Unlock()
return 0, false, syserr.ErrMessageTooLong
}
if l > free {
// Message can't fit right now, and could not be truncated.
q.mu.Unlock()
return 0, false, syserr.ErrWouldBlock
}
// Aggregate l bytes of data. This will truncate the data if l is less than
// the total bytes held in data.
v := make([]byte, l)
for i, b := 0, v; i < len(data) && len(b) > 0; i++ {
n := copy(b, data[i])
b = b[n:]
}
notify = true
q.used += l
if c.Rights != nil {
// `c` is passed by value, but `c.Rights` is an interface over a
// pointer pointing to the same `RightsControlMessage` implementation
// (e.g. `*RightsFiles`) regardless of `c` being passed by value.
// Here we empty that underlying shared `RightsControlMessage`
// implementation and allocate a new one specifically so that we can
// queue it as such with the queued `ControlMessages` being the only
// one that has it.
c.Rights = c.Rights.TransferRights()
}
q.dataList.PushBack(&message{
Data: v,
Control: c,
Address: from,
})
q.mu.Unlock()
return l, notify, err
}
// Dequeue removes the first entry in the data queue, if one exists.
//
// If notify is true, writerWaiters.Notify must be called:
// q.writerWaiters.Notify(waiter.WritableEvents)
func (q *queue) Dequeue() (e *message, notify bool, err *syserr.Error) {
q.mu.Lock()
if q.dataList.Front() == nil {
err := syserr.ErrWouldBlock
if q.closed.RacyLoad() {
err = syserr.ErrClosedForReceive
if q.unread {
err = syserr.ErrConnectionReset
}
}
q.mu.Unlock()
return nil, false, err
}
e = q.dataList.Front()
q.dataList.Remove(e)
q.used -= e.Length()
notify = q.bufWritable()
q.mu.Unlock()
return e, notify, nil
}
// Peek returns the first entry in the data queue, if one exists.
func (q *queue) Peek() (*message, *syserr.Error) {
q.mu.Lock()
defer q.mu.Unlock()
if q.dataList.Front() == nil {
err := syserr.ErrWouldBlock
if q.closed.RacyLoad() {
if err = syserr.ErrClosedForReceive; q.unread {
err = syserr.ErrConnectionReset
}
}
return nil, err
}
return q.dataList.Front().Peek(), nil
}
// QueuedSize returns the number of bytes currently in the queue, that is, the
// number of readable bytes.
func (q *queue) QueuedSize() int64 {
q.mu.Lock()
defer q.mu.Unlock()
return q.used
}
// MaxQueueSize returns the maximum number of bytes storable in the queue.
func (q *queue) MaxQueueSize() int64 {
q.mu.Lock()
defer q.mu.Unlock()
return q.limit
}
// SetMaxQueueSize sets the maximum number of bytes storable in the queue.
func (q *queue) SetMaxQueueSize(v int64) {
q.mu.Lock()
defer q.mu.Unlock()
q.limit = v
}
// CloseUnread sets flag to indicate that the peer is closed (not shutdown)
// with unread data. So if read on this queue shall return ECONNRESET error.
func (q *queue) CloseUnread() {
q.mu.Lock()
defer q.mu.Unlock()
q.unread = true
}