github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/grid/grid.go (about) 1 // Copyright (c) 2015-2023 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 // Package grid provides single-connection two-way grid communication. 19 package grid 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "io" 26 "sync" 27 "time" 28 29 "github.com/gobwas/ws/wsutil" 30 ) 31 32 // ErrDisconnected is returned when the connection to the remote has been lost during the call. 33 var ErrDisconnected = RemoteErr("remote disconnected") 34 35 const ( 36 // minBufferSize is the minimum buffer size. 37 // Buffers below this is not reused. 38 minBufferSize = 1 << 10 39 40 // defaultBufferSize is the default buffer allocation size. 41 defaultBufferSize = 4 << 10 42 43 // maxBufferSize is the maximum buffer size. 44 // Buffers larger than this is not reused. 45 maxBufferSize = 64 << 10 46 47 // If there is a queue, merge up to this many messages. 48 maxMergeMessages = 30 49 50 // clientPingInterval will ping the remote handler every 15 seconds. 51 // Clients disconnect when we exceed 2 intervals. 52 clientPingInterval = 15 * time.Second 53 54 // Deadline for single (non-streaming) requests to complete. 55 // Used if no deadline is provided on context. 56 defaultSingleRequestTimeout = time.Minute 57 ) 58 59 var internalByteBuffer = sync.Pool{ 60 New: func() any { 61 m := make([]byte, 0, defaultBufferSize) 62 return &m 63 }, 64 } 65 66 // GetByteBuffer can be replaced with a function that returns a small 67 // byte buffer. 68 // When replacing PutByteBuffer should also be replaced 69 // There is no minimum size. 70 var GetByteBuffer = func() []byte { 71 b := *internalByteBuffer.Get().(*[]byte) 72 return b[:0] 73 } 74 75 // PutByteBuffer is for returning byte buffers. 76 var PutByteBuffer = func(b []byte) { 77 if cap(b) >= minBufferSize && cap(b) < maxBufferSize { 78 internalByteBuffer.Put(&b) 79 } 80 } 81 82 // readAllInto reads from r and appends to b until an error or EOF and returns the data it read. 83 // A successful call returns err == nil, not err == EOF. Because readAllInto is 84 // defined to read from src until EOF, it does not treat an EOF from Read 85 // as an error to be reported. 86 func readAllInto(b []byte, r *wsutil.Reader) ([]byte, error) { 87 for { 88 if len(b) == cap(b) { 89 // Add more capacity (let append pick how much). 90 b = append(b, 0)[:len(b)] 91 } 92 n, err := r.Read(b[len(b):cap(b)]) 93 b = b[:len(b)+n] 94 if err != nil { 95 if errors.Is(err, io.EOF) { 96 err = nil 97 } 98 return b, err 99 } 100 } 101 } 102 103 // getDeadline will truncate the deadline so it is at least 1ms and at most MaxDeadline. 104 func getDeadline(d time.Duration) time.Duration { 105 if d < time.Millisecond { 106 return 0 107 } 108 if d > MaxDeadline { 109 return MaxDeadline 110 } 111 return d 112 } 113 114 type writerWrapper struct { 115 ch chan<- []byte 116 ctx context.Context 117 } 118 119 func (w *writerWrapper) Write(p []byte) (n int, err error) { 120 buf := GetByteBuffer() 121 if cap(buf) < len(p) { 122 PutByteBuffer(buf) 123 buf = make([]byte, len(p)) 124 } 125 buf = buf[:len(p)] 126 copy(buf, p) 127 select { 128 case w.ch <- buf: 129 return len(p), nil 130 case <-w.ctx.Done(): 131 return 0, context.Cause(w.ctx) 132 } 133 } 134 135 // WriterToChannel will return an io.Writer that writes to the given channel. 136 // The context both allows returning errors on writes and to ensure that 137 // this isn't abandoned if the channel is no longer being read from. 138 func WriterToChannel(ctx context.Context, ch chan<- []byte) io.Writer { 139 return &writerWrapper{ch: ch, ctx: ctx} 140 } 141 142 // bytesOrLength returns small (<=100b) byte slices as string, otherwise length. 143 func bytesOrLength(b []byte) string { 144 if len(b) > 100 { 145 return fmt.Sprintf("%d bytes", len(b)) 146 } 147 return fmt.Sprint(b) 148 }