Skip to content

Commit d0f2727

Browse files
authored
get rid of some panics (#1526)
* client: simplify (*HostClient).do() Remove an allocation in favour of deferring a call to release the response. * client: remove panic in dialAddr Return an error instead of panicking if the user supplied a nonsensical DialFunc. * compression: remove panic on invalid compression level If a compression level exceeding gzip's boundaries is provided, fasthttp will panic. Instead it would be better to handle this error for them by limiting it to the minimum or maximum value, depending on the direction the user has exceeded the limits. Clamp the value of gzip to always be between gzip.BestSpeed and gzip.BestCompression. * peripconn: remove panic on negative connection count When a negative count is reached when unregistering a connection, a panic is caused even though data-integrity is not at risk. Replace the panic() with a simple clamp on the value to ensure the value does not exceed it's expected lower bounds. References: #1504 * compress: remove error on failed nonblocking writes Since there is no way of handling or even logging non-critical errors in stateless non-blocking writecalls, just drop them and hope the user notices and tries again. * workerPool: remove panic on redundant Start and Stop calls Instead of panicking for invalid behaviour, it's preferable to just turn the function into a noop. * http: remove panic on invalid form boundary * http: remove panic on negative reads Since bufio already panics on negative reads, it is not necessary to do so as well. If the length is zero and for some reason no error is returned, readBodyIdentity and appendBodyFixedSize now errors in these cases. Link: https://github.com/golang/go/blob/851f6fd61425c810959c7ab51e6dc86f8a63c970/src/bufio/bufio.go#L246 * fs: remove panic on negative reader count When a negative count is reached when unregistering a reader, a panic is thrown even though data-integrity is not at risk. Replace the panic() with a simple clamp on the value to ensure the value does not exceed it's expected lower bounds. * server: remove panic in favour of a segfault Panicking with "BUG: " obscures the error. As the segfault causes a panic anyway, just let the chaos unfold. * server: remove panic in favour of returning an error Writing on a timed-out response is not endangering data integrity and just fails. * chore: add comments to all panics * chore: fix minor typo
1 parent 5209cc3 commit d0f2727

13 files changed

+60
-63
lines changed

brotli.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,7 @@ func nonblockingWriteBrotli(ctxv interface{}) {
138138
ctx := ctxv.(*compressCtx)
139139
zw := acquireRealBrotliWriter(ctx.w, ctx.level)
140140

141-
_, err := zw.Write(ctx.p)
142-
if err != nil {
143-
panic(fmt.Sprintf("BUG: brotli.Writer.Write for len(p)=%d returned unexpected error: %v", len(ctx.p), err))
144-
}
141+
zw.Write(ctx.p) //nolint:errcheck // no way to handle this error anyway
145142

146143
releaseRealBrotliWriter(zw, ctx.level)
147144
}

bytesconv.go

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
7979
copy(dst, net.IPv4zero)
8080
dst = dst.To4()
8181
if dst == nil {
82+
// developer sanity-check
8283
panic("BUG: dst must not be nil")
8384
}
8485

@@ -126,6 +127,7 @@ func ParseHTTPDate(date []byte) (time.Time, error) {
126127
// AppendUint appends n to dst and returns the extended dst.
127128
func AppendUint(dst []byte, n int) []byte {
128129
if n < 0 {
130+
// developer sanity-check
129131
panic("BUG: int must be positive")
130132
}
131133

@@ -281,6 +283,7 @@ var hexIntBufPool sync.Pool
281283

282284
func writeHexInt(w *bufio.Writer, n int) error {
283285
if n < 0 {
286+
// developer sanity-check
284287
panic("BUG: int must be positive")
285288
}
286289

client.go

+4-7
Original file line numberDiff line numberDiff line change
@@ -1294,26 +1294,23 @@ func isIdempotent(req *Request) bool {
12941294
}
12951295

12961296
func (c *HostClient) do(req *Request, resp *Response) (bool, error) {
1297-
nilResp := false
12981297
if resp == nil {
1299-
nilResp = true
13001298
resp = AcquireResponse()
1299+
defer ReleaseResponse(resp)
13011300
}
13021301

13031302
ok, err := c.doNonNilReqResp(req, resp)
13041303

1305-
if nilResp {
1306-
ReleaseResponse(resp)
1307-
}
1308-
13091304
return ok, err
13101305
}
13111306

13121307
func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error) {
13131308
if req == nil {
1309+
// for debugging purposes
13141310
panic("BUG: req cannot be nil")
13151311
}
13161312
if resp == nil {
1313+
// for debugging purposes
13171314
panic("BUG: resp cannot be nil")
13181315
}
13191316

@@ -1994,7 +1991,7 @@ func dialAddr(addr string, dial DialFunc, dialDualStack, isTLS bool, tlsConfig *
19941991
return nil, err
19951992
}
19961993
if conn == nil {
1997-
panic("BUG: DialFunc returned (nil, nil)")
1994+
return nil, errors.New("dialling unsuccessful. Please report this bug!")
19981995
}
19991996

20001997
// We assume that any conn that has the Handshake() method is a TLS conn already.

compress.go

+19-10
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func releaseFlateReader(zr io.ReadCloser) {
6666
func resetFlateReader(zr io.ReadCloser, r io.Reader) error {
6767
zrr, ok := zr.(zlib.Resetter)
6868
if !ok {
69+
// sanity check. should only be called with a zlib.Reader
6970
panic("BUG: zlib.Reader doesn't implement zlib.Resetter???")
7071
}
7172
return zrr.Reset(r, nil)
@@ -101,7 +102,14 @@ func acquireRealGzipWriter(w io.Writer, level int) *gzip.Writer {
101102
if v == nil {
102103
zw, err := gzip.NewWriterLevel(w, level)
103104
if err != nil {
104-
panic(fmt.Sprintf("BUG: unexpected error from gzip.NewWriterLevel(%d): %v", level, err))
105+
// gzip.NewWriterLevel only errors for invalid
106+
// compression levels. Clamp it to be min or max.
107+
if level < gzip.HuffmanOnly {
108+
level = gzip.HuffmanOnly
109+
} else {
110+
level = gzip.BestCompression
111+
}
112+
zw, _ = gzip.NewWriterLevel(w, level)
105113
}
106114
return zw
107115
}
@@ -175,10 +183,7 @@ func nonblockingWriteGzip(ctxv interface{}) {
175183
ctx := ctxv.(*compressCtx)
176184
zw := acquireRealGzipWriter(ctx.w, ctx.level)
177185

178-
_, err := zw.Write(ctx.p)
179-
if err != nil {
180-
panic(fmt.Sprintf("BUG: gzip.Writer.Write for len(p)=%d returned unexpected error: %v", len(ctx.p), err))
181-
}
186+
zw.Write(ctx.p) //nolint:errcheck // no way to handle this error anyway
182187

183188
releaseRealGzipWriter(zw, ctx.level)
184189
}
@@ -271,10 +276,7 @@ func nonblockingWriteDeflate(ctxv interface{}) {
271276
ctx := ctxv.(*compressCtx)
272277
zw := acquireRealDeflateWriter(ctx.w, ctx.level)
273278

274-
_, err := zw.Write(ctx.p)
275-
if err != nil {
276-
panic(fmt.Sprintf("BUG: zlib.Writer.Write for len(p)=%d returned unexpected error: %v", len(ctx.p), err))
277-
}
279+
zw.Write(ctx.p) //nolint:errcheck // no way to handle this error anyway
278280

279281
releaseRealDeflateWriter(zw, ctx.level)
280282
}
@@ -379,7 +381,14 @@ func acquireRealDeflateWriter(w io.Writer, level int) *zlib.Writer {
379381
if v == nil {
380382
zw, err := zlib.NewWriterLevel(w, level)
381383
if err != nil {
382-
panic(fmt.Sprintf("BUG: unexpected error from zlib.NewWriterLevel(%d): %v", level, err))
384+
// zlib.NewWriterLevel only errors for invalid
385+
// compression levels. Clamp it to be min or max.
386+
if level < zlib.HuffmanOnly {
387+
level = zlib.HuffmanOnly
388+
} else {
389+
level = zlib.BestCompression
390+
}
391+
zw, _ = zlib.NewWriterLevel(w, level)
383392
}
384393
return zw
385394
}

fs.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ func (ff *fsFile) decReadersCount() {
602602
ff.h.cacheLock.Lock()
603603
ff.readersCount--
604604
if ff.readersCount < 0 {
605-
panic("BUG: negative fsFile.readersCount!")
605+
ff.readersCount = 0
606606
}
607607
ff.h.cacheLock.Unlock()
608608
}
@@ -1395,6 +1395,7 @@ func readFileHeader(f *os.File, compressed bool, fileEncoding string) ([]byte, e
13951395
func stripLeadingSlashes(path []byte, stripSlashes int) []byte {
13961396
for stripSlashes > 0 && len(path) > 0 {
13971397
if path[0] != '/' {
1398+
// developer sanity-check
13981399
panic("BUG: path must start with slash")
13991400
}
14001401
n := bytes.IndexByte(path[1:], '/')

http.go

+15-11
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
963963
// Do not care about memory allocations here, since multipart
964964
// form processing is slow.
965965
if len(boundary) == 0 {
966-
panic("BUG: form boundary cannot be empty")
966+
return errors.New("form boundary cannot be empty")
967967
}
968968

969969
mw := multipart.NewWriter(w)
@@ -2134,13 +2134,14 @@ func readBodyIdentity(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, err
21342134
for {
21352135
nn, err := r.Read(dst[offset:])
21362136
if nn <= 0 {
2137-
if err != nil {
2138-
if err == io.EOF {
2139-
return dst[:offset], nil
2140-
}
2137+
switch {
2138+
case errors.Is(err, io.EOF):
2139+
return dst[:offset], nil
2140+
case err != nil:
21412141
return dst[:offset], err
2142+
default:
2143+
return dst[:offset], fmt.Errorf("bufio.Read() returned (%d, nil)", nn)
21422144
}
2143-
panic(fmt.Sprintf("BUG: bufio.Read() returned (%d, nil)", nn))
21442145
}
21452146
offset += nn
21462147
if maxBodySize > 0 && offset > maxBodySize {
@@ -2175,13 +2176,14 @@ func appendBodyFixedSize(r *bufio.Reader, dst []byte, n int) ([]byte, error) {
21752176
for {
21762177
nn, err := r.Read(dst[offset:])
21772178
if nn <= 0 {
2178-
if err != nil {
2179-
if err == io.EOF {
2180-
err = io.ErrUnexpectedEOF
2181-
}
2179+
switch {
2180+
case errors.Is(err, io.EOF):
2181+
return dst[:offset], io.ErrUnexpectedEOF
2182+
case err != nil:
21822183
return dst[:offset], err
2184+
default:
2185+
return dst[:offset], fmt.Errorf("bufio.Read() returned (%d, nil)", nn)
21832186
}
2184-
panic(fmt.Sprintf("BUG: bufio.Read() returned (%d, nil)", nn))
21852187
}
21862188
offset += nn
21872189
if offset == dstLen {
@@ -2197,6 +2199,8 @@ type ErrBrokenChunk struct {
21972199

21982200
func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) {
21992201
if len(dst) > 0 {
2202+
// data integrity might be in danger. No idea what we received,
2203+
// but nothing we should write to.
22002204
panic("BUG: expected zero-length buffer")
22012205
}
22022206

lbclient.go

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ func (cc *LBClient) init() {
8484
cc.mu.Lock()
8585
defer cc.mu.Unlock()
8686
if len(cc.Clients) == 0 {
87+
// developer sanity-check
8788
panic("BUG: LBClient.Clients cannot be empty")
8889
}
8990
for _, c := range cc.Clients {

peripconn.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package fasthttp
22

33
import (
4-
"fmt"
54
"net"
65
"sync"
76
)
@@ -25,17 +24,16 @@ func (cc *perIPConnCounter) Register(ip uint32) int {
2524

2625
func (cc *perIPConnCounter) Unregister(ip uint32) {
2726
cc.lock.Lock()
27+
defer cc.lock.Unlock()
2828
if cc.m == nil {
29-
cc.lock.Unlock()
29+
// developer safeguard
3030
panic("BUG: perIPConnCounter.Register() wasn't called")
3131
}
3232
n := cc.m[ip] - 1
3333
if n < 0 {
34-
cc.lock.Unlock()
35-
panic(fmt.Sprintf("BUG: negative per-ip counter=%d for ip=%d", n, ip))
34+
n = 0
3635
}
3736
cc.m[ip] = n
38-
cc.lock.Unlock()
3937
}
4038

4139
type perIPConn struct {

peripconn_test.go

-14
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ func TestPerIPConnCounter(t *testing.T) {
2525

2626
var cc perIPConnCounter
2727

28-
expectPanic(t, func() { cc.Unregister(123) })
29-
3028
for i := 1; i < 100; i++ {
3129
if n := cc.Register(123); n != i {
3230
t.Fatalf("Unexpected counter value=%d. Expected %d", n, i)
@@ -43,21 +41,9 @@ func TestPerIPConnCounter(t *testing.T) {
4341
}
4442
cc.Unregister(456)
4543

46-
expectPanic(t, func() { cc.Unregister(123) })
47-
expectPanic(t, func() { cc.Unregister(456) })
48-
4944
n = cc.Register(123)
5045
if n != 1 {
5146
t.Fatalf("Unexpected counter value=%d. Expected 1", n)
5247
}
5348
cc.Unregister(123)
5449
}
55-
56-
func expectPanic(t *testing.T, f func()) {
57-
defer func() {
58-
if r := recover(); r == nil {
59-
t.Fatalf("Expecting panic")
60-
}
61-
}()
62-
f()
63-
}

server.go

+8-9
Original file line numberDiff line numberDiff line change
@@ -1929,9 +1929,6 @@ func acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net.
19291929
for {
19301930
c, err := ln.Accept()
19311931
if err != nil {
1932-
if c != nil {
1933-
panic("BUG: net.Listener returned non-nil conn and non-nil error")
1934-
}
19351932
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
19361933
s.logger().Printf("Timeout error when accepting new connections: %v", netErr)
19371934
time.Sleep(time.Second)
@@ -1943,9 +1940,6 @@ func acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net.
19431940
}
19441941
return nil, io.EOF
19451942
}
1946-
if c == nil {
1947-
panic("BUG: net.Listener returned (nil, nil)")
1948-
}
19491943

19501944
if tc, ok := c.(*net.TCPConn); ok && s.TCPKeepalive {
19511945
if err := tc.SetKeepAlive(s.TCPKeepalive); err != nil {
@@ -2578,7 +2572,7 @@ func (ctx *RequestCtx) LastTimeoutErrorResponse() *Response {
25782572

25792573
func writeResponse(ctx *RequestCtx, w *bufio.Writer) error {
25802574
if ctx.timeoutResponse != nil {
2581-
panic("BUG: cannot write timed out response")
2575+
return errors.New("cannot write timed out response")
25822576
}
25832577
err := ctx.Response.Write(w)
25842578

@@ -2596,8 +2590,8 @@ func acquireByteReader(ctxP **RequestCtx) (*bufio.Reader, error) {
25962590
c := ctx.c
25972591
s.releaseCtx(ctx)
25982592

2599-
// Make GC happy, so it could garbage collect ctx
2600-
// while we waiting for the next request.
2593+
// Make GC happy, so it could garbage collect ctx while we wait for the
2594+
// next request.
26012595
ctx = nil
26022596
*ctxP = nil
26032597

@@ -2612,6 +2606,7 @@ func acquireByteReader(ctxP **RequestCtx) (*bufio.Reader, error) {
26122606
return nil, io.EOF
26132607
}
26142608
if n != 1 {
2609+
// developer sanity-check
26152610
panic("BUG: Reader must return at least one byte")
26162611
}
26172612

@@ -2787,19 +2782,23 @@ func (fa *fakeAddrer) LocalAddr() net.Addr {
27872782
}
27882783

27892784
func (fa *fakeAddrer) Read(p []byte) (int, error) {
2785+
// developer sanity-check
27902786
panic("BUG: unexpected Read call")
27912787
}
27922788

27932789
func (fa *fakeAddrer) Write(p []byte) (int, error) {
2790+
// developer sanity-check
27942791
panic("BUG: unexpected Write call")
27952792
}
27962793

27972794
func (fa *fakeAddrer) Close() error {
2795+
// developer sanity-check
27982796
panic("BUG: unexpected Close call")
27992797
}
28002798

28012799
func (s *Server) releaseCtx(ctx *RequestCtx) {
28022800
if ctx.timeoutResponse != nil {
2801+
// developer sanity-check
28032802
panic("BUG: cannot release timed out RequestCtx")
28042803
}
28052804

stackless/func.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
// at the moment due to high load.
2121
func NewFunc(f func(ctx interface{})) func(ctx interface{}) bool {
2222
if f == nil {
23+
// developer sanity-check
2324
panic("BUG: f cannot be nil")
2425
}
2526

timer.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ func initTimer(t *time.Timer, timeout time.Duration) *time.Timer {
1010
return time.NewTimer(timeout)
1111
}
1212
if t.Reset(timeout) {
13+
// developer sanity-check
1314
panic("BUG: active timer trapped into initTimer()")
1415
}
1516
return t

workerpool.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type workerChan struct {
4747

4848
func (wp *workerPool) Start() {
4949
if wp.stopCh != nil {
50-
panic("BUG: workerPool already started")
50+
return
5151
}
5252
wp.stopCh = make(chan struct{})
5353
stopCh := wp.stopCh
@@ -72,7 +72,7 @@ func (wp *workerPool) Start() {
7272

7373
func (wp *workerPool) Stop() {
7474
if wp.stopCh == nil {
75-
panic("BUG: workerPool wasn't started")
75+
return
7676
}
7777
close(wp.stopCh)
7878
wp.stopCh = nil

0 commit comments

Comments
 (0)