2021-04-18 15:41:13 -04:00
|
|
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
|
|
|
//
|
|
|
|
// This file is part of MinIO Object Storage stack
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2017-11-07 18:18:59 -05:00
|
|
|
|
|
|
|
// Package ioutil implements some I/O utility functions which are not covered
|
|
|
|
// by the standard library.
|
|
|
|
package ioutil
|
|
|
|
|
|
|
|
import (
|
2021-03-18 17:09:55 -04:00
|
|
|
"context"
|
2023-09-26 14:20:06 -04:00
|
|
|
"errors"
|
2017-11-07 18:18:59 -05:00
|
|
|
"io"
|
2019-05-22 16:47:15 -04:00
|
|
|
"os"
|
2024-01-28 13:04:17 -05:00
|
|
|
"runtime/debug"
|
2021-11-02 11:11:50 -04:00
|
|
|
"sync"
|
2021-03-18 17:09:55 -04:00
|
|
|
"time"
|
2018-01-31 16:17:24 -05:00
|
|
|
|
2023-12-09 13:17:51 -05:00
|
|
|
"github.com/dustin/go-humanize"
|
2021-06-01 17:59:40 -04:00
|
|
|
"github.com/minio/minio/internal/disk"
|
2017-11-07 18:18:59 -05:00
|
|
|
)
|
|
|
|
|
2023-12-09 13:17:51 -05:00
|
|
|
// Block sizes constant.
|
|
|
|
const (
|
|
|
|
BlockSizeSmall = 32 * humanize.KiByte // Default r/w block size for smaller objects.
|
2024-03-15 15:27:59 -04:00
|
|
|
BlockSizeLarge = 1 * humanize.MiByte // Default r/w block size for normal objects.
|
|
|
|
BlockSizeReallyLarge = 4 * humanize.MiByte // Default r/w block size for very large objects.
|
2023-12-09 13:17:51 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// aligned sync.Pool's
|
|
|
|
var (
|
|
|
|
ODirectPoolXLarge = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
b := disk.AlignedBlock(BlockSizeReallyLarge)
|
|
|
|
return &b
|
|
|
|
},
|
|
|
|
}
|
|
|
|
ODirectPoolLarge = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
b := disk.AlignedBlock(BlockSizeLarge)
|
|
|
|
return &b
|
|
|
|
},
|
|
|
|
}
|
|
|
|
ODirectPoolSmall = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
b := disk.AlignedBlock(BlockSizeSmall)
|
|
|
|
return &b
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2017-11-07 18:18:59 -05:00
|
|
|
// WriteOnCloser implements io.WriteCloser and always
|
2019-12-27 05:21:32 -05:00
|
|
|
// executes at least one write operation if it is closed.
|
2017-11-07 18:18:59 -05:00
|
|
|
//
|
|
|
|
// This can be useful within the context of HTTP. At least
|
|
|
|
// one write operation must happen to send the HTTP headers
|
|
|
|
// to the peer.
|
|
|
|
type WriteOnCloser struct {
|
|
|
|
io.Writer
|
|
|
|
hasWritten bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *WriteOnCloser) Write(p []byte) (int, error) {
|
|
|
|
w.hasWritten = true
|
|
|
|
return w.Writer.Write(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the WriteOnCloser. It behaves like io.Closer.
|
|
|
|
func (w *WriteOnCloser) Close() error {
|
|
|
|
if !w.hasWritten {
|
|
|
|
_, err := w.Write(nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if closer, ok := w.Writer.(io.Closer); ok {
|
|
|
|
return closer.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasWritten returns true if at least one write operation was performed.
|
|
|
|
func (w *WriteOnCloser) HasWritten() bool { return w.hasWritten }
|
|
|
|
|
|
|
|
// WriteOnClose takes an io.Writer and returns an ioutil.WriteOnCloser.
|
|
|
|
func WriteOnClose(w io.Writer) *WriteOnCloser {
|
|
|
|
return &WriteOnCloser{w, false}
|
|
|
|
}
|
2018-02-23 18:07:21 -05:00
|
|
|
|
2024-01-24 13:08:31 -05:00
|
|
|
type ioret[V any] struct {
|
|
|
|
val V
|
2021-03-18 17:09:55 -04:00
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2023-07-28 18:37:53 -04:00
|
|
|
// DeadlineWriter deadline writer with timeout
|
2021-03-18 17:09:55 -04:00
|
|
|
type DeadlineWriter struct {
|
|
|
|
io.WriteCloser
|
|
|
|
timeout time.Duration
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
2024-01-24 13:08:31 -05:00
|
|
|
// WithDeadline will execute a function with a deadline and return a value of a given type.
|
|
|
|
// If the deadline/context passes before the function finishes executing,
|
|
|
|
// the zero value and the context error is returned.
|
|
|
|
func WithDeadline[V any](ctx context.Context, timeout time.Duration, work func(ctx context.Context) (result V, err error)) (result V, err error) {
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
c := make(chan ioret[V], 1)
|
|
|
|
go func() {
|
|
|
|
v, err := work(ctx)
|
|
|
|
c <- ioret[V]{val: v, err: err}
|
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case v := <-c:
|
|
|
|
return v.val, v.err
|
|
|
|
case <-ctx.Done():
|
|
|
|
var zero V
|
|
|
|
return zero, ctx.Err()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-25 19:58:31 -04:00
|
|
|
// DeadlineWorker implements the deadline/timeout resiliency pattern.
|
|
|
|
type DeadlineWorker struct {
|
|
|
|
timeout time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewDeadlineWorker constructs a new DeadlineWorker with the given timeout.
|
2024-01-24 13:08:31 -05:00
|
|
|
// To return values, use the WithDeadline helper instead.
|
2023-07-25 19:58:31 -04:00
|
|
|
func NewDeadlineWorker(timeout time.Duration) *DeadlineWorker {
|
2024-01-24 13:08:31 -05:00
|
|
|
dw := &DeadlineWorker{
|
2023-07-25 19:58:31 -04:00
|
|
|
timeout: timeout,
|
|
|
|
}
|
2024-01-24 13:08:31 -05:00
|
|
|
return dw
|
2023-07-25 19:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run runs the given function, passing it a stopper channel. If the deadline passes before
|
2024-01-24 13:08:31 -05:00
|
|
|
// the function finishes executing, Run returns context.DeadlineExceeded to the caller.
|
|
|
|
// channel so that the work function can attempt to exit gracefully.
|
|
|
|
// Multiple calls to Run will run independently of each other.
|
2023-07-25 19:58:31 -04:00
|
|
|
func (d *DeadlineWorker) Run(work func() error) error {
|
2024-01-24 13:08:31 -05:00
|
|
|
c := make(chan ioret[struct{}], 1)
|
2023-07-25 19:58:31 -04:00
|
|
|
t := time.NewTimer(d.timeout)
|
|
|
|
go func() {
|
2024-01-24 13:08:31 -05:00
|
|
|
c <- ioret[struct{}]{val: struct{}{}, err: work()}
|
2023-07-25 19:58:31 -04:00
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case r := <-c:
|
|
|
|
if !t.Stop() {
|
|
|
|
<-t.C
|
|
|
|
}
|
|
|
|
return r.err
|
|
|
|
case <-t.C:
|
2023-07-28 18:37:53 -04:00
|
|
|
return context.DeadlineExceeded
|
2023-07-25 19:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-18 17:09:55 -04:00
|
|
|
// NewDeadlineWriter wraps a writer to make it respect given deadline
|
|
|
|
// value per Write(). If there is a blocking write, the returned Writer
|
|
|
|
// will return whenever the timer hits (the return values are n=0
|
2023-07-28 18:37:53 -04:00
|
|
|
// and err=context.DeadlineExceeded.)
|
2021-03-18 17:09:55 -04:00
|
|
|
func NewDeadlineWriter(w io.WriteCloser, timeout time.Duration) io.WriteCloser {
|
|
|
|
return &DeadlineWriter{WriteCloser: w, timeout: timeout}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *DeadlineWriter) Write(buf []byte) (int, error) {
|
|
|
|
if w.err != nil {
|
|
|
|
return 0, w.err
|
|
|
|
}
|
|
|
|
|
2024-02-02 06:02:31 -05:00
|
|
|
n, err := WithDeadline[int](context.Background(), w.timeout, func(ctx context.Context) (int, error) {
|
|
|
|
return w.WriteCloser.Write(buf)
|
|
|
|
})
|
|
|
|
w.err = err
|
|
|
|
return n, err
|
2021-03-18 17:09:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Close closer interface to close the underlying closer
|
|
|
|
func (w *DeadlineWriter) Close() error {
|
2024-02-02 06:02:31 -05:00
|
|
|
err := w.WriteCloser.Close()
|
|
|
|
w.err = err
|
|
|
|
if err == nil {
|
|
|
|
w.err = errors.New("we are closed") // Avoids any reuse on the Write() side.
|
|
|
|
}
|
|
|
|
return err
|
2021-03-18 17:09:55 -04:00
|
|
|
}
|
|
|
|
|
2018-02-23 18:07:21 -05:00
|
|
|
// LimitWriter implements io.WriteCloser.
|
|
|
|
//
|
|
|
|
// This is implemented such that we want to restrict
|
|
|
|
// an enscapsulated writer upto a certain length
|
|
|
|
// and skip a certain number of bytes.
|
|
|
|
type LimitWriter struct {
|
|
|
|
io.Writer
|
|
|
|
skipBytes int64
|
|
|
|
wLimit int64
|
|
|
|
}
|
|
|
|
|
2020-03-18 19:19:29 -04:00
|
|
|
// Write implements the io.Writer interface limiting upto
|
2018-02-23 18:07:21 -05:00
|
|
|
// configured length, also skips the first N bytes.
|
|
|
|
func (w *LimitWriter) Write(p []byte) (n int, err error) {
|
|
|
|
n = len(p)
|
|
|
|
var n1 int
|
|
|
|
if w.skipBytes > 0 {
|
|
|
|
if w.skipBytes >= int64(len(p)) {
|
2021-11-16 12:28:29 -05:00
|
|
|
w.skipBytes -= int64(len(p))
|
2018-02-23 18:07:21 -05:00
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
p = p[w.skipBytes:]
|
|
|
|
w.skipBytes = 0
|
|
|
|
}
|
|
|
|
if w.wLimit == 0 {
|
|
|
|
return n, nil
|
|
|
|
}
|
|
|
|
if w.wLimit < int64(len(p)) {
|
|
|
|
n1, err = w.Writer.Write(p[:w.wLimit])
|
2021-11-16 12:28:29 -05:00
|
|
|
w.wLimit -= int64(n1)
|
2018-02-23 18:07:21 -05:00
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
n1, err = w.Writer.Write(p)
|
2021-11-16 12:28:29 -05:00
|
|
|
w.wLimit -= int64(n1)
|
2018-02-23 18:07:21 -05:00
|
|
|
return n, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the LimitWriter. It behaves like io.Closer.
|
|
|
|
func (w *LimitWriter) Close() error {
|
|
|
|
if closer, ok := w.Writer.(io.Closer); ok {
|
|
|
|
return closer.Close()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LimitedWriter takes an io.Writer and returns an ioutil.LimitWriter.
|
|
|
|
func LimitedWriter(w io.Writer, skipBytes int64, limit int64) *LimitWriter {
|
|
|
|
return &LimitWriter{w, skipBytes, limit}
|
|
|
|
}
|
2018-03-02 20:24:02 -05:00
|
|
|
|
|
|
|
type nopCloser struct {
|
|
|
|
io.Writer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (nopCloser) Close() error { return nil }
|
|
|
|
|
|
|
|
// NopCloser returns a WriteCloser with a no-op Close method wrapping
|
|
|
|
// the provided Writer w.
|
|
|
|
func NopCloser(w io.Writer) io.WriteCloser {
|
|
|
|
return nopCloser{w}
|
|
|
|
}
|
2018-09-20 22:22:09 -04:00
|
|
|
|
|
|
|
// SkipReader skips a given number of bytes and then returns all
|
|
|
|
// remaining data.
|
|
|
|
type SkipReader struct {
|
|
|
|
io.Reader
|
|
|
|
|
|
|
|
skipCount int64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *SkipReader) Read(p []byte) (int, error) {
|
|
|
|
l := int64(len(p))
|
|
|
|
if l == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
for s.skipCount > 0 {
|
|
|
|
if l > s.skipCount {
|
|
|
|
l = s.skipCount
|
|
|
|
}
|
|
|
|
n, err := s.Reader.Read(p[:l])
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
s.skipCount -= int64(n)
|
|
|
|
}
|
|
|
|
return s.Reader.Read(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSkipReader - creates a SkipReader
|
|
|
|
func NewSkipReader(r io.Reader, n int64) io.Reader {
|
|
|
|
return &SkipReader{r, n}
|
|
|
|
}
|
2019-05-22 16:47:15 -04:00
|
|
|
|
2021-11-02 11:11:50 -04:00
|
|
|
var copyBufPool = sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
b := make([]byte, 32*1024)
|
|
|
|
return &b
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2024-01-18 02:03:17 -05:00
|
|
|
// Copy is exactly like io.Copy but with reusable buffers.
|
2021-11-02 11:11:50 -04:00
|
|
|
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
|
|
|
bufp := copyBufPool.Get().(*[]byte)
|
|
|
|
buf := *bufp
|
|
|
|
defer copyBufPool.Put(bufp)
|
|
|
|
|
|
|
|
return io.CopyBuffer(dst, src, buf)
|
|
|
|
}
|
|
|
|
|
2019-10-25 13:37:53 -04:00
|
|
|
// SameFile returns if the files are same.
|
|
|
|
func SameFile(fi1, fi2 os.FileInfo) bool {
|
|
|
|
if !os.SameFile(fi1, fi2) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if !fi1.ModTime().Equal(fi2.ModTime()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if fi1.Mode() != fi2.Mode() {
|
|
|
|
return false
|
|
|
|
}
|
2022-01-24 14:28:45 -05:00
|
|
|
return fi1.Size() == fi2.Size()
|
2019-10-25 13:37:53 -04:00
|
|
|
}
|
|
|
|
|
2021-09-29 19:40:28 -04:00
|
|
|
// DirectioAlignSize - DirectIO alignment needs to be 4K. Defined here as
|
2019-05-22 16:47:15 -04:00
|
|
|
// directio.AlignSize is defined as 0 in MacOS causing divide by 0 error.
|
2021-09-29 19:40:28 -04:00
|
|
|
const DirectioAlignSize = 4096
|
2019-05-22 16:47:15 -04:00
|
|
|
|
|
|
|
// CopyAligned - copies from reader to writer using the aligned input
|
|
|
|
// buffer, it is expected that input buffer is page aligned to
|
|
|
|
// 4K page boundaries. Without passing aligned buffer may cause
|
|
|
|
// this function to return error.
|
|
|
|
//
|
fix: use buffers only when necessary for io.Copy() (#11229)
Use separate sync.Pool for writes/reads
Avoid passing buffers for io.CopyBuffer()
if the writer or reader implement io.WriteTo or io.ReadFrom
respectively then its useless for sync.Pool to allocate
buffers on its own since that will be completely ignored
by the io.CopyBuffer Go implementation.
Improve this wherever we see this to be optimal.
This allows us to be more efficient on memory usage.
```
385 // copyBuffer is the actual implementation of Copy and CopyBuffer.
386 // if buf is nil, one is allocated.
387 func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
388 // If the reader has a WriteTo method, use it to do the copy.
389 // Avoids an allocation and a copy.
390 if wt, ok := src.(WriterTo); ok {
391 return wt.WriteTo(dst)
392 }
393 // Similarly, if the writer has a ReadFrom method, use it to do the copy.
394 if rt, ok := dst.(ReaderFrom); ok {
395 return rt.ReadFrom(src)
396 }
```
From readahead package
```
// WriteTo writes data to w until there's no more data to write or when an error occurs.
// The return value n is the number of bytes written.
// Any error encountered during the write is also returned.
func (a *reader) WriteTo(w io.Writer) (n int64, err error) {
if a.err != nil {
return 0, a.err
}
n = 0
for {
err = a.fill()
if err != nil {
return n, err
}
n2, err := w.Write(a.cur.buffer())
a.cur.inc(n2)
n += int64(n2)
if err != nil {
return n, err
}
```
2021-01-06 12:36:55 -05:00
|
|
|
// This code is similar in spirit to io.Copy but it is only to be
|
2019-05-22 16:47:15 -04:00
|
|
|
// used with DIRECT I/O based file descriptor and it is expected that
|
|
|
|
// input writer *os.File not a generic io.Writer. Make sure to have
|
|
|
|
// the file opened for writes with syscall.O_DIRECT flag.
|
2022-03-09 14:38:54 -05:00
|
|
|
func CopyAligned(w io.Writer, r io.Reader, alignedBuf []byte, totalSize int64, file *os.File) (int64, error) {
|
2023-09-26 14:20:06 -04:00
|
|
|
if totalSize == 0 {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2019-05-22 16:47:15 -04:00
|
|
|
var written int64
|
|
|
|
for {
|
2019-08-23 18:36:46 -04:00
|
|
|
buf := alignedBuf
|
2023-09-26 14:20:06 -04:00
|
|
|
if totalSize > 0 {
|
2019-08-23 18:36:46 -04:00
|
|
|
remaining := totalSize - written
|
|
|
|
if remaining < int64(len(buf)) {
|
|
|
|
buf = buf[:remaining]
|
2019-05-22 16:47:15 -04:00
|
|
|
}
|
|
|
|
}
|
2023-09-26 14:20:06 -04:00
|
|
|
|
2019-08-23 18:36:46 -04:00
|
|
|
nr, err := io.ReadFull(r, buf)
|
2023-09-26 14:20:06 -04:00
|
|
|
eof := errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
|
2019-08-23 18:36:46 -04:00
|
|
|
if err != nil && !eof {
|
|
|
|
return written, err
|
|
|
|
}
|
2023-09-26 14:20:06 -04:00
|
|
|
|
2019-08-23 18:36:46 -04:00
|
|
|
buf = buf[:nr]
|
2024-03-15 15:27:59 -04:00
|
|
|
var (
|
|
|
|
n int
|
|
|
|
un int
|
|
|
|
nw int64
|
|
|
|
)
|
|
|
|
|
|
|
|
remain := len(buf) % DirectioAlignSize
|
|
|
|
if remain == 0 {
|
2019-08-23 18:36:46 -04:00
|
|
|
// buf is aligned for directio write()
|
2021-03-17 12:38:38 -04:00
|
|
|
n, err = w.Write(buf)
|
|
|
|
nw = int64(n)
|
2019-08-23 18:36:46 -04:00
|
|
|
} else {
|
2024-03-15 15:27:59 -04:00
|
|
|
if remain < len(buf) {
|
|
|
|
n, err = w.Write(buf[:len(buf)-remain])
|
|
|
|
if err != nil {
|
|
|
|
return written, err
|
|
|
|
}
|
|
|
|
nw = int64(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disable O_DIRECT on fd's on unaligned buffer
|
|
|
|
// perform an amortized Fdatasync(fd) on the fd at
|
|
|
|
// the end, this is performed by the caller before
|
|
|
|
// closing 'w'.
|
|
|
|
if err = disk.DisableDirectIO(file); err != nil {
|
|
|
|
return written, err
|
|
|
|
}
|
|
|
|
|
2019-08-23 18:36:46 -04:00
|
|
|
// buf is not aligned, hence use writeUnaligned()
|
2024-03-15 15:27:59 -04:00
|
|
|
// for the remainder
|
|
|
|
un, err = w.Write(buf[len(buf)-remain:])
|
|
|
|
nw += int64(un)
|
2019-08-23 18:36:46 -04:00
|
|
|
}
|
2023-09-26 14:20:06 -04:00
|
|
|
|
2019-08-23 18:36:46 -04:00
|
|
|
if nw > 0 {
|
2021-03-17 12:38:38 -04:00
|
|
|
written += nw
|
2019-08-23 18:36:46 -04:00
|
|
|
}
|
2023-09-26 14:20:06 -04:00
|
|
|
|
2019-08-23 18:36:46 -04:00
|
|
|
if err != nil {
|
|
|
|
return written, err
|
|
|
|
}
|
2023-09-26 14:20:06 -04:00
|
|
|
|
2021-03-17 12:38:38 -04:00
|
|
|
if nw != int64(len(buf)) {
|
2019-08-23 18:36:46 -04:00
|
|
|
return written, io.ErrShortWrite
|
|
|
|
}
|
|
|
|
|
2023-09-26 14:20:06 -04:00
|
|
|
if totalSize > 0 && written == totalSize {
|
|
|
|
// we have written the entire stream, return right here.
|
|
|
|
return written, nil
|
2019-08-23 18:36:46 -04:00
|
|
|
}
|
2023-09-26 14:20:06 -04:00
|
|
|
|
2019-08-23 18:36:46 -04:00
|
|
|
if eof {
|
2023-09-26 14:20:06 -04:00
|
|
|
// We reached EOF prematurely but we did not write everything
|
|
|
|
// that we promised that we would write.
|
|
|
|
if totalSize > 0 && written != totalSize {
|
|
|
|
return written, io.ErrUnexpectedEOF
|
|
|
|
}
|
2019-08-23 18:36:46 -04:00
|
|
|
return written, nil
|
2019-05-22 16:47:15 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-01-28 13:04:17 -05:00
|
|
|
|
|
|
|
// SafeClose safely closes any channel of any type
|
|
|
|
func SafeClose[T any](c chan<- T) {
|
|
|
|
if c != nil {
|
|
|
|
close(c)
|
2024-01-28 21:10:27 -05:00
|
|
|
return
|
2024-01-28 13:04:17 -05:00
|
|
|
}
|
|
|
|
// Print stack to check who is sending `c` as `nil`
|
|
|
|
// without crashing the server.
|
|
|
|
debug.PrintStack()
|
|
|
|
}
|