Skip to content

Commit b128174

Browse files
committed
Closing a single connection instead of discarding whole connection pool
1 parent 264dfde commit b128174

File tree

6 files changed

+41
-90
lines changed

6 files changed

+41
-90
lines changed

Diff for: CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Release v1.2.13 (2023-07-13)
2+
===
3+
* On errors close the single connection instead of destroying the whole pool
4+
* Increase number of retries to 3 for health checks
5+
* Fix issue where we end up sending error to a closed channel
6+
17
Release v1.2.12 (2023-01-10)
28
===
39
* Add support for healthcheck infrastructure

Diff for: dax/internal/client/cluster.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ func (c *cluster) update(config []serviceEndpoint) error {
642642
for ep, clicfg := range oldActive {
643643
_, isPartOfUpdatedEndpointsConfig := newEndpoints[ep]
644644
if !isPartOfUpdatedEndpointsConfig {
645-
c.debugLog(fmt.Sprintf("Found updated endpoing configs, will close inactive endpoint client : %s", ep.host))
645+
c.debugLog(fmt.Sprintf("Found updated endpoint configs, will close inactive endpoint client : %s", ep.host))
646646
toClose = append(toClose, clicfg)
647647
}
648648
}

Diff for: dax/internal/client/single.go

+21-10
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ func (client *SingleDaxClient) startHealthChecks(cc *cluster, host hostPort) {
169169
ctx, cfn := context.WithTimeout(aws.BackgroundContext(), 1*time.Second)
170170
defer cfn()
171171
var err error
172-
_, err = client.endpoints(RequestOptions{MaxRetries: 2, Context: ctx})
172+
_, err = client.endpoints(RequestOptions{MaxRetries: 3, Context: ctx})
173173
if err != nil {
174174
cc.debugLog(fmt.Sprintf("Health checks failed with error " + err.Error() + " for host :: " + host.host))
175175
cc.onHealthCheckFailed(host)
@@ -740,31 +740,41 @@ func (client *SingleDaxClient) executeWithContext(ctx aws.Context, op string, en
740740
return err
741741
}
742742
if err = client.pool.setDeadline(ctx, t); err != nil {
743-
client.pool.discard(t)
743+
// If the error is just due to context cancelled or timeout
744+
// then the tube is still usable because we have not written anything to tube
745+
if err == ctx.Err() {
746+
client.pool.put(t)
747+
return err
748+
}
749+
// If we get error while setting deadline of tube
750+
// probably something is wrong with the tube
751+
client.pool.closeTube(t)
744752
return err
745753
}
746754

747755
if err = client.auth(t); err != nil {
748-
client.pool.discard(t)
756+
// Auth method writes in the tube and
757+
// it is not guaranteed that it will be drained completely on error
758+
client.pool.closeTube(t)
749759
return err
750760
}
751761

752762
writer := t.CborWriter()
753763
if err = encoder(writer); err != nil {
754-
// Validation errors will cause pool to be discarded as there is no guarantee
764+
// Validation errors will cause connection to be closed as there is no guarantee
755765
// that the validation was performed before any data was written into tube
756-
client.pool.discard(t)
766+
client.pool.closeTube(t)
757767
return err
758768
}
759769
if err := writer.Flush(); err != nil {
760-
client.pool.discard(t)
770+
client.pool.closeTube(t)
761771
return err
762772
}
763773

764774
reader := t.CborReader()
765775
ex, err := decodeError(reader)
766-
if err != nil { // decode or network error
767-
client.pool.discard(t)
776+
if err != nil { // decode or network error - doesn't guarantee completely drained tube
777+
client.pool.closeTube(t)
768778
return err
769779
}
770780
if ex != nil { // user or server error
@@ -774,7 +784,8 @@ func (client *SingleDaxClient) executeWithContext(ctx aws.Context, op string, en
774784

775785
err = decoder(reader)
776786
if err != nil {
777-
client.pool.discard(t)
787+
// we are not able to completely drain tube
788+
client.pool.closeTube(t)
778789
} else {
779790
client.pool.put(t)
780791
}
@@ -809,7 +820,7 @@ func (client *SingleDaxClient) recycleTube(t tube, err error) {
809820
if recycle {
810821
client.pool.put(t)
811822
} else {
812-
client.pool.discard(t)
823+
client.pool.closeTube(t)
813824
}
814825
}
815826
func (client *SingleDaxClient) auth(t tube) error {

Diff for: dax/internal/client/tube.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
)
2626

2727
const magic = "J7yne5G"
28-
const agent = "DaxGoClient-1.2.12"
28+
const agent = "DaxGoClient-1.2.13"
2929

3030
var optional = map[string]string{"UserAgent": agent}
3131

Diff for: dax/internal/client/tubepool.go

+7-31
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,13 @@ func (p *tubePool) allocAndReleaseGate(session int64, done chan tube, releaseGat
190190
}
191191
} else {
192192
p.mutex.Lock()
193-
cls := p.closed
194-
p.mutex.Unlock()
195-
if !cls {
193+
if !p.closed {
196194
select {
197195
case p.errCh <- err:
198196
default:
199197
}
200198
}
199+
p.mutex.Unlock()
201200
}
202201
if done != nil {
203202
close(done)
@@ -236,9 +235,10 @@ func (p *tubePool) put(t tube) {
236235
p.top = t
237236
}
238237

239-
// Closes the specified tube, and if the tube is using the same version as the current session,
240-
// then also closes all other idle tubes and performs a version bump.
241-
func (p *tubePool) discard(t tube) {
238+
// Make sure to closeTube the tube if you are not sure that the tube is clean
239+
// Clean tube means nothing is written inside the tube or
240+
// the things written inside tube is drained completely
241+
func (p *tubePool) closeTube(t tube) {
242242
if t == nil {
243243
return
244244
}
@@ -249,30 +249,6 @@ func (p *tubePool) discard(t tube) {
249249
t.Close()
250250
}()
251251
}
252-
253-
p.mutex.Lock()
254-
255-
var head tube
256-
if t.Session() == p.session {
257-
p.sessionBump()
258-
head = p.clearIdleConnections()
259-
}
260-
261-
// Waiters enter the waiting queue when there's no existing tube
262-
// or when they failed to acquire a permit to create a new tube.
263-
// There's also a chance the newly created tube was stolen and
264-
// the thief must return it back into the pool or discard it.
265-
if p.waiters != nil {
266-
select {
267-
case p.waiters <- nil: // wake up a single waiter, if any
268-
break
269-
default:
270-
close(p.waiters) // or unblock all future waiters who are yet to enter the waiters queue
271-
p.waiters = nil
272-
}
273-
}
274-
p.mutex.Unlock()
275-
p.closeAll(head)
276252
}
277253

278254
// Sets the deadline on the underlying net.Conn object
@@ -303,7 +279,7 @@ func (p *tubePool) Close() error {
303279
p.waiters = nil
304280
}
305281
close(p.errCh)
306-
// cannot close(p.gate) as send on closed channel will panic. new connections will be closed immediately.
282+
// cannot closeTube(p.gate) as send on closed channel will panic. new connections will be closed immediately.
307283
}
308284
p.mutex.Unlock()
309285
p.closeAll(head)

Diff for: dax/internal/client/tubepool_test.go

+5-47
Original file line numberDiff line numberDiff line change
@@ -509,58 +509,16 @@ func countTubes(pool *tubePool) int {
509509
return count
510510
}
511511

512-
func TestTubePool_DiscardBumpsSession(t *testing.T) {
512+
func TestTubePool_close(t *testing.T) {
513513
p := newTubePoolWithOptions(":1234", tubePoolOptions{1, 5 * time.Second, defaultDialer.DialContext}, connConfigData)
514514
origSession := p.session
515+
p.closeTubeImmediately = true
515516

516517
tt := &mockTube{}
517-
tt.On("Session").Return(p.session).Once()
518518
tt.On("Close").Return(nil).Once()
519-
p.discard(tt)
520-
521-
require.NotEqual(t, origSession, p.session)
522-
}
523-
524-
func TestTubePool_DiscardWakesUpWaiters(t *testing.T) {
525-
526-
p := newTubePoolWithOptions(":1234", tubePoolOptions{1, 5 * time.Second, defaultDialer.DialContext}, connConfigData)
527-
p.dialContext = func(ctx context.Context, a, n string) (net.Conn, error) {
528-
return &mockConn{}, nil
529-
}
530-
// artificially enter the gate to prevent new connections
531-
entered := p.gate.tryEnter()
532-
require.True(t, entered)
533-
534-
var startedWg sync.WaitGroup
535-
startedWg.Add(1)
536-
537-
ch := make(chan struct {
538-
tube
539-
error
540-
})
541-
go func() {
542-
startedWg.Done()
543-
t, err := p.get()
544-
ch <- struct {
545-
tube
546-
error
547-
}{t, err}
548-
}()
549-
startedWg.Wait()
550-
// wait some extra time to make sure the caller has entered waiters queue
551-
time.Sleep(2 * time.Second)
552-
553-
// release the gate to allow woken waiters to establish a new connection
554-
p.gate.exit()
555-
tt := &mockTube{}
556-
tt.On("Session").Return(p.session).Once()
557-
tt.On("Close").Return(nil).Once()
558-
559-
p.discard(tt)
560-
561-
result := <-ch
562-
require.NoError(t, result.error)
563-
require.NotNil(t, result.tube)
519+
p.closeTube(tt)
520+
require.Equal(t, origSession, p.session)
521+
tt.AssertCalled(t, "Close")
564522
}
565523

566524
func TestTubePool_PutClosesTubesIfPoolIsClosed(t *testing.T) {

0 commit comments

Comments
 (0)