Skip to content

Commit d238853

Browse files
committed
api: replaced Future.done with a sync.Cond
This commit reduces allocations. Future.done allocation replaced with - Future.cond (*sync.Cond) - Future.finished (atomic.Bool) Other code use Future.isDone() instead (Future.done == nil) check. Added Future.finish() marks Future as done. Future.WaitChan() now creates channel on demand. Closes #496
1 parent aff7842 commit d238853

File tree

4 files changed

+68
-32
lines changed

4 files changed

+68
-32
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1212

1313
* New types for MessagePack extensions compatible with go-option (#459).
1414
* Added `box.MustNew` wrapper for `box.New` without an error (#448).
15+
* Added Future.cond (sync.Cond) and Future.finished bool. Added Future.finish() marks Future as done (#496).
1516

1617
### Changed
1718

@@ -23,8 +24,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
2324
* Removed deprecated `box.session.push()` support: Future.AppendPush()
2425
and Future.GetIterator() methods, ResponseIterator and TimeoutResponseIterator types,
2526
Future.pushes[], Future.ready (#480, #497).
26-
* `LogAppendPushFailed` replaced with `LogBoxSessionPushUnsupported` (#480)
27-
* Removed deprecated `Connection` methods, related interfaces and tests are updated (#479)
27+
* `LogAppendPushFailed` replaced with `LogBoxSessionPushUnsupported` (#480).
28+
* Removed deprecated `Connection` methods, related interfaces and tests are updated (#479).
29+
* Future.done replaced with Future.cond (sync.Cond) + Future.finished bool (#496).
2830

2931
### Fixed
3032

MIGRATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ TODO
1515
* Removed `box.session.push()` support: Future.AppendPush() and Future.GetIterator()
1616
methods, ResponseIterator and TimeoutResponseIterator types.
1717
* Removed deprecated `Connection` methods, related interfaces and tests are updated.
18+
1819
*NOTE*: due to Future.GetTyped() doesn't decode SelectRequest into structure, substitute Connection.GetTyped() following the example:
1920
```Go
2021
var singleTpl = Tuple{}
@@ -30,6 +31,7 @@ TODO
3031
).GetTyped(&tpl)
3132
singleTpl := tpl[0]
3233
```
34+
* Future.done replaced with Future.cond (sync.Cond) + Future.finished bool.
3335

3436
## Migration from v1.x.x to v2.x.x
3537

connection.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ func (conn *Connection) newFuture(req Request) (fut *Future) {
934934
ErrRateLimited,
935935
"Request is rate limited on client",
936936
}
937-
fut.done = nil
937+
fut.finish()
938938
return
939939
}
940940
}
@@ -948,23 +948,23 @@ func (conn *Connection) newFuture(req Request) (fut *Future) {
948948
ErrConnectionClosed,
949949
"using closed connection",
950950
}
951-
fut.done = nil
951+
fut.finish()
952952
shard.rmut.Unlock()
953953
return
954954
case connDisconnected:
955955
fut.err = ClientError{
956956
ErrConnectionNotReady,
957957
"client connection is not ready",
958958
}
959-
fut.done = nil
959+
fut.finish()
960960
shard.rmut.Unlock()
961961
return
962962
case connShutdown:
963963
fut.err = ClientError{
964964
ErrConnectionShutdown,
965965
"server shutdown in progress",
966966
}
967-
fut.done = nil
967+
fut.finish()
968968
shard.rmut.Unlock()
969969
return
970970
}
@@ -993,7 +993,7 @@ func (conn *Connection) newFuture(req Request) (fut *Future) {
993993
runtime.Gosched()
994994
select {
995995
case conn.rlimit <- struct{}{}:
996-
case <-fut.done:
996+
case <-fut.WaitChan():
997997
if fut.err == nil {
998998
panic("fut.done is closed, but err is nil")
999999
}
@@ -1007,17 +1007,16 @@ func (conn *Connection) newFuture(req Request) (fut *Future) {
10071007
// is "done" before the response is come.
10081008
func (conn *Connection) contextWatchdog(fut *Future, ctx context.Context) {
10091009
select {
1010-
case <-fut.done:
1010+
case <-fut.WaitChan():
10111011
case <-ctx.Done():
10121012
}
10131013

1014-
select {
1015-
case <-fut.done:
1014+
if fut.isDone() {
10161015
return
1017-
default:
1018-
conn.cancelFuture(fut, fmt.Errorf("context is done (request ID %d): %w",
1019-
fut.requestId, context.Cause(ctx)))
10201016
}
1017+
1018+
conn.cancelFuture(fut, fmt.Errorf("context is done (request ID %d): %w",
1019+
fut.requestId, context.Cause(ctx)))
10211020
}
10221021

10231022
func (conn *Connection) incrementRequestCnt() {
@@ -1034,7 +1033,7 @@ func (conn *Connection) send(req Request, streamId uint64) *Future {
10341033
conn.incrementRequestCnt()
10351034

10361035
fut := conn.newFuture(req)
1037-
if fut.done == nil {
1036+
if fut.isDone() {
10381037
conn.decrementRequestCnt()
10391038
return fut
10401039
}
@@ -1057,12 +1056,12 @@ func (conn *Connection) putFuture(fut *Future, req Request, streamId uint64) {
10571056
shardn := fut.requestId & (conn.opts.Concurrency - 1)
10581057
shard := &conn.shard[shardn]
10591058
shard.bufmut.Lock()
1060-
select {
1061-
case <-fut.done:
1059+
1060+
if fut.isDone() {
10621061
shard.bufmut.Unlock()
10631062
return
1064-
default:
10651063
}
1064+
10661065
firstWritten := shard.buf.Len() == 0
10671066
if shard.buf.Cap() == 0 {
10681067
shard.buf.b = make([]byte, 0, 128)

future.go

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tarantool
33
import (
44
"io"
55
"sync"
6+
"sync/atomic"
67
"time"
78
)
89

@@ -15,32 +16,43 @@ type Future struct {
1516
mutex sync.Mutex
1617
resp Response
1718
err error
19+
cond sync.Cond
20+
finished atomic.Bool
1821
done chan struct{}
1922
}
2023

2124
func (fut *Future) wait() {
22-
if fut.done == nil {
23-
return
25+
fut.mutex.Lock()
26+
defer fut.mutex.Unlock()
27+
28+
for !fut.isDone() {
29+
fut.cond.Wait()
2430
}
25-
<-fut.done
2631
}
2732

2833
func (fut *Future) isDone() bool {
29-
if fut.done == nil {
30-
return true
31-
}
32-
select {
33-
case <-fut.done:
34-
return true
35-
default:
36-
return false
34+
return fut.finished.Load()
35+
}
36+
37+
func (fut *Future) finish() {
38+
fut.mutex.Lock()
39+
defer fut.mutex.Unlock()
40+
41+
fut.finished.Store(true)
42+
43+
if fut.done != nil {
44+
close(fut.done)
3745
}
46+
47+
fut.cond.Broadcast()
3848
}
3949

4050
// NewFuture creates a new empty Future for a given Request.
4151
func NewFuture(req Request) (fut *Future) {
4252
fut = &Future{}
43-
fut.done = make(chan struct{})
53+
fut.done = nil
54+
fut.finished.Store(false)
55+
fut.cond = *sync.NewCond(&fut.mutex)
4456
fut.req = req
4557
return fut
4658
}
@@ -60,7 +72,14 @@ func (fut *Future) SetResponse(header Header, body io.Reader) error {
6072
}
6173
fut.resp = resp
6274

63-
close(fut.done)
75+
fut.finished.Store(true)
76+
77+
if fut.done != nil {
78+
close(fut.done)
79+
}
80+
81+
fut.cond.Broadcast()
82+
6483
return nil
6584
}
6685

@@ -74,7 +93,13 @@ func (fut *Future) SetError(err error) {
7493
}
7594
fut.err = err
7695

77-
close(fut.done)
96+
fut.finished.Store(true)
97+
98+
if fut.done != nil {
99+
close(fut.done)
100+
}
101+
102+
fut.cond.Broadcast()
78103
}
79104

80105
// GetResponse waits for Future to be filled and returns Response and error.
@@ -122,8 +147,16 @@ func init() {
122147

123148
// WaitChan returns channel which becomes closed when response arrived or error occurred.
124149
func (fut *Future) WaitChan() <-chan struct{} {
125-
if fut.done == nil {
150+
fut.mutex.Lock()
151+
defer fut.mutex.Unlock()
152+
153+
if fut.isDone() {
126154
return closedChan
127155
}
156+
157+
if fut.done == nil {
158+
fut.done = make(chan struct{})
159+
}
160+
128161
return fut.done
129162
}

0 commit comments

Comments
 (0)