I am trying to debug a very unusual error I am receiving for a simple REST library I wrote.
I am using the standard net/http package to make Get, Post, Put, Delete requests but my tests occasionally fail when I make multiple requests successively. My test looks like this:
func TestGetObject(t *testing.T) {
firebaseRoot := New(firebase_url)
body, err := firebaseRoot.Get("1")
if err != nil {
t.Errorf("Error: %s", err)
}
t.Logf("%q", body)
}
func TestPushObject(t *testing.T) {
firebaseRoot := New(firebase_url)
msg := Message{"testing", "1..2..3"}
body, err := firebaseRoot.Push("/", msg)
if err != nil {
t.Errorf("Error: %s", err)
}
t.Logf("%q", body)
}
And I am making the request like this:
// Send HTTP Request, return data
func (f *firebaseRoot) SendRequest(method string, path string, body io.Reader) ([]byte, error) {
url := f.BuildURL(path)
// create a request
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
// send JSON to firebase
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Bad HTTP Response: %v", resp.Status)
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return b, nil
}
Sometimes it works, but most of the time I get 1 or 2 failures:
--- FAIL: TestGetObject (0.00 seconds)
firebase_test.go:53: Error: Get https://go-firebase-test.firebaseio.com/1.json: EOF
firebase_test.go:55: ""
--- FAIL: TestPushObject (0.00 seconds)
firebase_test.go:63: Error: Post https://go-firebase-test.firebaseio.com/.json: EOF
firebase_test.go:65: ""
FAIL
exit status 1
FAIL github.com/chourobin/go.firebase 3.422s
The failures happen when I make more than 1 request. If I comment out everything except for the PUT request, the tests consistently pass. Once I include a second test, such as GET, one or the other fails (sometimes both pass).
What version of Go are you using (go version
)?
$ go version go version go1.18.3 darwin/amd64
Does this issue reproduce with the latest release?
yes
What operating system and processor architecture are you using (go env
)?
go env
Output
$ go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/Users/mitr/Library/Caches/go-build" GOENV="/Users/mitr/Library/Application Support/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOINSECURE="" GOMODCACHE="/Users/mitr/Go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="darwin" GOPATH="/Users/mitr/Go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64" GOVCS="" GOVERSION="go1.18.3" GCCGO="gccgo" GOAMD64="v1" AR="ar" CC="clang" CXX="clang++" CGO_ENABLED="1" GOMOD="/Users/mitr/Go/src/github.com/containers/image/go.mod" GOWORK="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/tp/yfcwvlb55vx8lkv5gppb43cm0000gn/T/go-build3737075247=/tmp/go-build -gno-record-gcc-switches -fno-common"
What did you do?
Given a HTTP server that reads the request, but (cleanly) closes the connection without producing any response:
package main import ( "fmt" "log" "net" "net/http" ) func server(ln net.Listener) { for { conn, err := ln.Accept() if err != nil { return } log.Printf("%v: Accepted", conn.RemoteAddr()) var buf [4096]byte // Hopefully enough for a full header n, err := conn.Read(buf[:]) // Completely read and ignore the header if err != nil { panic(err) } log.Printf("%v: Read %d", conn.RemoteAddr(), n) err = conn.Close() if err != nil { panic(err) } log.Printf("%v: Closed", conn.RemoteAddr()) } } func main() { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { panic(err) } go server(ln) res, err := http.Get(fmt.Sprintf("http://%s/", ln.Addr().String())) fmt.Printf("res=%#v, err=%v (%#v)", res, err, err) }
What did you expect to see?
An error saying something about an unexpectedly closed connection.
What did you see instead?
2022/06/21 02:28:30 127.0.0.1:64135: Accepted
2022/06/21 02:28:30 127.0.0.1:64135: Read 96
2022/06/21 02:28:30 127.0.0.1:64135: Closed
res=(*http.Response)(nil), err=Get "http://127.0.0.1:64134/": EOF (&url.Error{Op:"Get", URL:"http://127.0.0.1:64134/", Err:(*errors.errorString)(0xc000098060)})
i.e. the error is io.EOF
, which seems inconsistent with the official definition of that value:
Functions should return EOF only to signal a graceful end of input. If the EOF occurs unexpectedly in a structured data stream, the appropriate error is either ErrUnexpectedEOF or some other error giving more detail.
Notes
I appreciate that this might not be possible to change due to the compatibility promise.
The immediate cause is
; if that returns io.EOF
, it is wrapped in
err = transportReadFromServerError{err} |
, but later only unwrapped to become raw io.EOF
again, with no logic anywhere to turn it into an “this was unexpected” error.
The day before yesterday, there was a miraculous error in the process of the call, the error in the client side of the http request error Get "http://127.0.0.1:8800": EOF
, but the server side does not have any exception all the logs are normal execution
Since the error is only on the client side, the Google search results are not caused by the actual scenario (there is no suspicion that there is a problem on the server side), so we have no choice but to capture the packets, and finally the problem is solved
Server.Server write timeout is set to 10s, so by the time the handler finishes processing the request, the connection between the server and the client is already closed.
However, since the data written on the server side is much smaller than the write buffer size (4096 byte) set in the http/net package, the Write method of bufio does not return an error
Since the test environment is too complicated, I wrote a demo to reproduce the whole process, the following is the wireshark exported svc which can be seen:
|
|
b.bufr
: conn’s read buffer b.bufw
: conn’s write buffer, 4096 byte in size c.readRequest(ctx)
: the req request is processed, and a *response
is returned ServeHTTP(w, w.req)
: eventually w is passed all the way down all the way down, to our own processing function
Writer is the equivalent of calling w.w.Write(p []byte) == w.cw.Write(p []byte) w.cw
: which is of type chunkWriter so if the call to w.w.Write(p []byte) == chunkWriter.Write([]byte)
cw.res.conn
: According to the above code, we find that conn == w.conn == srv.newConn(rw) cw.res.conn.bufw
: that is c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4 “10), which shows that the buffer for conn write is 4096 byte
bufio
: if the length of the data does not exceed len(b.buf), the data will be copied to b.buf, and not actually written to b.wr
Loading
import "io"
- Overview
- Index
- Examples
- Subdirectories
Overview
Пакет io предоставляет основные интерфейсы к примитивам ввода/вывода.Его основная задача-обернуть существующие реализации таких примитивов,например,в пакете os,в общие публичные интерфейсы,которые абстрагируют функциональность,плюс некоторые другие связанные с ними примитивы.
Поскольку эти интерфейсы и примитивы обёртывают низкоуровневые операции с различными реализациями,если только информированные клиенты не предполагают,что они безопасны для параллельного выполнения,они не должны предполагать,что они безопасны для параллельного выполнения.
Index
- Constants
- Variables
- funcy(dst Writer,src Reader)(запись int64,ошибка)
- funcyBuffer(dst Writer,src Reader,buf []byte)(запись int64,ошибка)
- funcyN(dst Writer,src Reader,n int64)(запись int64,ошибка)
- func Pipe() (*PipeReader, *PipeWriter)
- func ReadAll(r Reader) ([]byte, error)
- включить функцию ReadAtLeast(r Reader,buf []byte,min int)(n int,ошибка)
- включить функцию ReadFull(r Reader,buf []byte)(n int,ошибка)
- funceString(w WriteString,s string)(n int,error)
- type ByteReader
- type ByteScanner
- type ByteWriter
- type Closer
- type LimitedReader
- func (l *LimitedReader)Read(p []byte)(n int,error)
- type PipeReader
- функция (r *PipeReader)Close()ошибка
- func (r *PipeReader) CloseWithError(err error) error
- func (r *PipeReader)Read(data []byte)(n int,error)
- type PipeWriter
- func (w *PipeWriter)Close()ошибка
- func (w *PipeWriter)CloseWithError(err error error)error
- func (w *PipeWriter)Write(data []byte)(n int,error)
- type ReadCloser
- func NopCloser(r Reader) ReadCloser
- type ReadSeekCloser
- type ReadSeeker
- type ReadWriteCloser
- type ReadWriteSeeker
- type ReadWriter
- type Reader
- функционал LimitReader(r Reader,n int64)Reader
- включить функцию MultiReader(readers …Reader)Reader
- функционал TeeReader(r Reader,w Writer)Reader
- type ReaderAt
- type ReaderFrom
- type RuneReader
- type RuneScanner
- type SectionReader
- включить NewSectionReader(r ReaderAt,off int64,n int64)*SectionReader
- func (s *SectionReader)Read(p []byte)(n int,error)
- func (s *SectionReader)ReadAt(p []byte,off int64)(n int,error)
- func (s *SectionReader)Seek(offset int64,whence int)(int64,ошибка)
- func (s *SectionReader) Size() int64
- type Seeker
- type StringWriter
- type WriteCloser
- type WriteSeeker
- type Writer
- Функция MultiWriter(writers …Writer)Writer
- type WriterAt
- type WriterTo
Package files
io.gomulti.gopipe.go
Constants
Ищите откуда значения.
const ( SeekStart = 0 SeekCurrent = 1 SeekEnd = 2 )
Variables
EOF-это ошибка,возвращаемая Read,когда входные данные больше недоступны.(Read должен возвращать сам EOF,а не ошибку,обертывающую EOF,поскольку вызывающие функции будут проверять наличие EOF с помощью ==).Функции должны возвращать EOF только для того,чтобы сигнализировать об изящном завершении ввода.Если EOF происходит неожиданно в структурированном потоке данных,соответствующей ошибкой будет либо ErrUnexpectedEOF,либо какая-то другая ошибка с более подробным описанием.
var EOF = errors.New("EOF")
ErrClosedPipe-это ошибка,используемая для операций чтения или записи на закрытой трубе.
var ErrClosedPipe = errors.New("io: read/write on closed pipe")
ErrNoProgress возвращается некоторыми клиентами Reader,когда многие вызовы Read не вернули никаких данных или возникла ошибка,что обычно является признаком неработающей реализации Reader.
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
ErrShortBuffer означает,что для чтения требовалось больше буфера,чем было предоставлено.
var ErrShortBuffer = errors.New("short buffer")
ErrShortWrite означает,что при записи было принято меньшее количество байт,чем запрашивалось,но не была возвращена явная ошибка.
var ErrShortWrite = errors.New("short write")
ErrUnexpectedEOF означает,что EOF встречалась в середине чтения блока фиксированного размера или структуры данных.
var ErrUnexpectedEOF = errors.New("unexpected EOF")
func Copy
func Copy(dst Writer, src Reader) (written int64, err error)
Копировать копии из src в dst до тех пор,пока не будет достигнута EOF на src или не произойдет ошибка.Она возвращает количество скопированных байтов и первую ошибку,возникшую при копировании,если таковая имела место.
Успешная Копия возвращает err ==nil,not err ==EOF.Так как Copy определено как читать из src до EOF,она не рассматривает EOF из Read как ошибку,о которой нужно сообщить.
Если src реализует интерфейс WriterTo,то копия реализуется вызовом src.WriteTo(dst).В противном случае,если dst реализует интерфейс ReaderFrom,то копия реализуется вызовом dst.ReadFrom(src).
Example
Code:
r := strings.NewReader("some io.Reader stream to be readn") if _, err := io.Copy(os.Stdout, r); err != nil { log.Fatal(err) }
Output:
some io.Reader stream to be read
func CopyBuffer1.5
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error)
CopyBuffer идентичен Copy за исключением того,что он проходит через предоставленный буфер (если он необходим),а не выделяет временный.Если buf равен нулю,то выделяется один,в противном случае,если он имеет нулевую длину,то CopyBuffer паникует.
Если либо src реализует WriterTo,либо dst реализует ReaderFrom,buf не будет использоваться для выполнения копирования.
Example
Code:
r1 := strings.NewReader("first readern") r2 := strings.NewReader("second readern") buf := make([]byte, 8) if _, err := io.CopyBuffer(os.Stdout, r1, buf); err != nil { log.Fatal(err) } if _, err := io.CopyBuffer(os.Stdout, r2, buf); err != nil { log.Fatal(err) }
Output:
first reader second reader
func CopyN
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)
CopyN копирует n байт (или до ошибки)из src в dst.Возвращает количество скопированных байтов и самую раннюю ошибку,обнаруженную при копировании.По возвращении записывается ==n if и только если ошибка ==nil.
Если dst реализует интерфейс ReaderFrom,то копия реализуется с его помощью.
Example
Code:
r := strings.NewReader("some io.Reader stream to be read") if _, err := io.CopyN(os.Stdout, r, 4); err != nil { log.Fatal(err) }
Output:
some
func Pipe
func Pipe() (*PipeReader, *PipeWriter)
Труба создает синхронную трубу в памяти.Она может быть использована для соединения кода,ожидающего io.Reader,с кодом,ожидающим io.Writer.
Чтения и записи на трубе сопоставляются один к одному,за исключением тех случаев,когда для потребления одной записи требуется несколько Чтений.Т.е.каждая запись в PipeWriter блокируется до тех пор,пока не будет выполнено одно или несколько Чтений с PipeReader,которые полностью поглощают записанные данные.Данные копируются непосредственно из Write на соответствующий Read (или Reads);внутренней буферизации нет.
Безопасно вызвать функцию Чтение и запись параллельно или с помощью функции Закрыть.Параллельные вызовы на Чтение и параллельные вызовы на Запись также безопасны:отдельные вызовы будут последовательно шлюзоваться.
Example
Code:
r, w := io.Pipe() go func() { fmt.Fprint(w, "some io.Reader stream to be readn") w.Close() }() if _, err := io.Copy(os.Stdout, r); err != nil { log.Fatal(err) }
Output:
some io.Reader stream to be read
func ReadAll1.16
func ReadAll(r Reader) ([]byte, error)
ReadAll считывает с r до ошибки или EOF и возвращает прочитанные данные.Успешный вызов возвращает err ==nil,not err ==EOF.Поскольку ReadAll определено как чтение из src до EOF,он не рассматривает EOF из Read как ошибку,о которой нужно сообщить.
Example
Code:
r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.") b, err := io.ReadAll(r) if err != nil { log.Fatal(err) } fmt.Printf("%s", b)
Output:
Go is a general-purpose language designed with systems programming in mind.
func ReadAtLeast
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
ReadAtLeast читает из r в buf, пока не прочитает не менее min байтов. Он возвращает количество скопированных байтов и ошибку, если было прочитано меньшее количество байтов. Ошибка — EOF только в том случае, если байты не были прочитаны. Если EOF происходит после чтения менее min байтов, ReadAtLeast возвращает ErrUnexpectedEOF. Если min больше, чем длина buf, ReadAtLeast возвращает ErrShortBuffer. При возврате n> = min тогда и только тогда, когда err == nil. Если r возвращает ошибку, прочитав не менее min байтов, ошибка сбрасывается.
Example
Code:
r := strings.NewReader("some io.Reader stream to be readn") buf := make([]byte, 14) if _, err := io.ReadAtLeast(r, buf, 4); err != nil { log.Fatal(err) } fmt.Printf("%sn", buf) shortBuf := make([]byte, 3) if _, err := io.ReadAtLeast(r, shortBuf, 4); err != nil { fmt.Println("error:", err) } longBuf := make([]byte, 64) if _, err := io.ReadAtLeast(r, longBuf, 64); err != nil { fmt.Println("error:", err) }
Output:
some io.Reader error: short buffer error: unexpected EOF
© Google, Inc.
Licensed under the Creative Commons Attribution License 3.0.
http://golang.org/pkg/io/
Go
1.19
-
Package goos
-
Package fs
-
func ReadFull
-
type ReaderAt