Skip to content

Commit 38fdfc7

Browse files
authored
fix(client): HTTP sender retries send empty request body (#38)
1 parent d282cfc commit 38fdfc7

6 files changed

+71
-30
lines changed

buffer.go

+11-15
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"bytes"
2929
"errors"
3030
"fmt"
31-
"io"
3231
"math"
3332
"math/big"
3433
"strconv"
@@ -122,20 +121,6 @@ func (b *buffer) writeBigInt(i *big.Int) {
122121
b.Write(s)
123122
}
124123

125-
// WriteTo wraps the built-in bytes.Buffer.WriteTo method
126-
// and writes the contents of the buffer to the provided
127-
// io.Writer
128-
func (b *buffer) WriteTo(w io.Writer) (int64, error) {
129-
n, err := b.Buffer.WriteTo(w)
130-
if err != nil {
131-
b.lastMsgPos -= int(n)
132-
return n, err
133-
}
134-
b.lastMsgPos = 0
135-
b.msgCount = 0
136-
return n, nil
137-
}
138-
139124
func (b *buffer) writeTableName(str string) error {
140125
if str == "" {
141126
return fmt.Errorf("table name cannot be empty: %w", errInvalidMsg)
@@ -386,6 +371,17 @@ func (b *buffer) prepareForField() bool {
386371
return true
387372
}
388373

374+
func (b *buffer) Bytes() []byte {
375+
return b.Buffer.Bytes()
376+
}
377+
378+
func (b *buffer) Reset() {
379+
b.Buffer.Reset()
380+
b.lastMsgPos = 0
381+
b.msgCount = 0
382+
b.resetMsgFlags()
383+
}
384+
389385
func (b *buffer) DiscardPendingMsg() {
390386
b.Truncate(b.lastMsgPos)
391387
b.resetMsgFlags()

http_sender.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package questdb
2626

2727
import (
28+
"bytes"
2829
"context"
2930
"crypto/tls"
3031
"encoding/json"
@@ -194,17 +195,20 @@ func (s *httpLineSender) flush0(ctx context.Context, closing bool) error {
194195
if s.buf.msgCount == 0 {
195196
return nil
196197
}
198+
// Always reset the buffer at the end of flush.
199+
defer s.buf.Reset()
197200

198201
// We rely on the following HTTP client implicit behavior:
199202
// s.buf implements WriteTo method which is used by the client.
200203
req, err = http.NewRequest(
201204
http.MethodPost,
202205
s.uri,
203-
&s.buf,
206+
bytes.NewReader(s.buf.Bytes()),
204207
)
205208
if err != nil {
206209
return err
207210
}
211+
req.ContentLength = int64(s.BufLen())
208212

209213
if s.user != "" && s.pass != "" {
210214
req.SetBasicAuth(s.user, s.pass)

http_sender_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,27 @@ func TestNoFlushWhenSenderIsClosedAndAutoFlushIsDisabled(t *testing.T) {
573573
assert.Empty(t, qdb.Messages(sender))
574574
}
575575

576+
func TestSuccessAfterRetries(t *testing.T) {
577+
ctx := context.Background()
578+
579+
srv, err := newTestHttpServer(failFirstThenSendToBackChannel)
580+
assert.NoError(t, err)
581+
defer srv.Close()
582+
583+
sender, err := qdb.NewLineSender(ctx, qdb.WithHttp(), qdb.WithAddress(srv.Addr()), qdb.WithRetryTimeout(time.Minute))
584+
assert.NoError(t, err)
585+
defer sender.Close(ctx)
586+
587+
err = sender.Table(testTable).Symbol("abc", "def").AtNow(ctx)
588+
assert.NoError(t, err)
589+
590+
err = sender.Flush(ctx)
591+
assert.NoError(t, err)
592+
593+
expectLines(t, srv.BackCh, []string{fmt.Sprintf("%s,abc=def", testTable)})
594+
assert.Zero(t, qdb.BufLen(sender))
595+
}
596+
576597
func TestBufferClearAfterFlush(t *testing.T) {
577598
ctx := context.Background()
578599

integration_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ func setupQuestDB0(ctx context.Context, auth ilpAuthType, setupProxy bool) (*que
132132
return nil, err
133133
}
134134
req := testcontainers.ContainerRequest{
135-
Image: "questdb/questdb:7.3.10",
135+
Image: "questdb/questdb:7.4.2",
136136
ExposedPorts: []string{"9000/tcp", "9009/tcp"},
137137
WaitingFor: wait.ForHTTP("/").WithPort("9000"),
138138
Networks: []string{networkName},

tcp_sender.go

+3
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ func (s *tcpLineSender) Flush(ctx context.Context) error {
205205
s.conn.SetWriteDeadline(time.Time{})
206206
}
207207

208+
// Always reset the buffer at the end of flush.
209+
defer s.buf.Reset()
210+
208211
if _, err := s.buf.WriteTo(s.conn); err != nil {
209212
return err
210213
}

utils_test.go

+30-13
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"net/http"
3535
"reflect"
3636
"sync"
37+
"sync/atomic"
3738
"testing"
3839
"time"
3940

@@ -43,11 +44,12 @@ import (
4344
type serverType int64
4445

4546
const (
46-
sendToBackChannel serverType = 0
47-
readAndDiscard serverType = 1
48-
returning500 serverType = 2
49-
returning403 serverType = 3
50-
returning404 serverType = 4
47+
sendToBackChannel serverType = 0
48+
readAndDiscard serverType = 1
49+
returning500 serverType = 2
50+
returning403 serverType = 3
51+
returning404 serverType = 4
52+
failFirstThenSendToBackChannel serverType = 5
5153
)
5254

5355
type testServer struct {
@@ -186,21 +188,21 @@ func (s *testServer) serveHttp() {
186188
}
187189
}()
188190

191+
var reqs int64
189192
http.Serve(s.tcpListener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
190193
var (
191194
err error
192195
)
193196

194197
switch s.serverType {
195-
case sendToBackChannel:
196-
r := bufio.NewReader(r.Body)
197-
var l string
198-
for err == nil {
199-
l, err = r.ReadString('\n')
200-
if err == nil && len(l) > 0 {
201-
lineFeed <- l[0 : len(l)-1]
202-
}
198+
case failFirstThenSendToBackChannel:
199+
if atomic.AddInt64(&reqs, 1) == 1 {
200+
w.WriteHeader(http.StatusInternalServerError)
201+
} else {
202+
err = readAndSendToBackChannel(r, lineFeed)
203203
}
204+
case sendToBackChannel:
205+
err = readAndSendToBackChannel(r, lineFeed)
204206
case readAndDiscard:
205207
_, err = io.Copy(io.Discard, r.Body)
206208
case returning500:
@@ -232,6 +234,21 @@ func (s *testServer) serveHttp() {
232234
}))
233235
}
234236

237+
func readAndSendToBackChannel(r *http.Request, lineFeed chan string) error {
238+
read := bufio.NewReader(r.Body)
239+
var (
240+
l string
241+
err error
242+
)
243+
for err == nil {
244+
l, err = read.ReadString('\n')
245+
if err == nil && len(l) > 0 {
246+
lineFeed <- l[0 : len(l)-1]
247+
}
248+
}
249+
return err
250+
}
251+
235252
func (s *testServer) Close() {
236253
close(s.closeCh)
237254
s.tcpListener.Close()

0 commit comments

Comments
 (0)