Skip to content

Commit dd9e78b

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 4231d9b commit dd9e78b

File tree

4 files changed

+67
-33
lines changed

4 files changed

+67
-33
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: 16 additions & 16 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,9 +993,10 @@ func (conn *Connection) newFuture(req Request) (fut *Future) {
993993
runtime.Gosched()
994994
select {
995995
case conn.rlimit <- struct{}{}:
996-
case <-fut.done:
996+
default:
997+
<-fut.WaitChan()
997998
if fut.err == nil {
998-
panic("fut.done is closed, but err is nil")
999+
panic("future WaitChan is closed, but err is nil")
9991000
}
10001001
}
10011002
}
@@ -1007,17 +1008,16 @@ func (conn *Connection) newFuture(req Request) (fut *Future) {
10071008
// is "done" before the response is come.
10081009
func (conn *Connection) contextWatchdog(fut *Future, ctx context.Context) {
10091010
select {
1010-
case <-fut.done:
1011+
case <-fut.WaitChan():
10111012
case <-ctx.Done():
10121013
}
10131014

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

10231023
func (conn *Connection) incrementRequestCnt() {
@@ -1034,7 +1034,7 @@ func (conn *Connection) send(req Request, streamId uint64) *Future {
10341034
conn.incrementRequestCnt()
10351035

10361036
fut := conn.newFuture(req)
1037-
if fut.done == nil {
1037+
if fut.isDone() {
10381038
conn.decrementRequestCnt()
10391039
return fut
10401040
}
@@ -1057,12 +1057,12 @@ func (conn *Connection) putFuture(fut *Future, req Request, streamId uint64) {
10571057
shardn := fut.requestId & (conn.opts.Concurrency - 1)
10581058
shard := &conn.shard[shardn]
10591059
shard.bufmut.Lock()
1060-
select {
1061-
case <-fut.done:
1060+
1061+
if fut.isDone() {
10621062
shard.bufmut.Unlock()
10631063
return
1064-
default:
10651064
}
1065+
10661066
firstWritten := shard.buf.Len() == 0
10671067
if shard.buf.Cap() == 0 {
10681068
shard.buf.b = make([]byte, 0, 128)

future.go

Lines changed: 45 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,13 @@ 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()
6482
return nil
6583
}
6684

@@ -74,7 +92,13 @@ func (fut *Future) SetError(err error) {
7492
}
7593
fut.err = err
7694

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

80104
// GetResponse waits for Future to be filled and returns Response and error.
@@ -122,8 +146,14 @@ func init() {
122146

123147
// WaitChan returns channel which becomes closed when response arrived or error occurred.
124148
func (fut *Future) WaitChan() <-chan struct{} {
125-
if fut.done == nil {
149+
fut.mutex.Lock()
150+
defer fut.mutex.Unlock()
151+
152+
if fut.isDone() {
126153
return closedChan
127154
}
155+
if fut.done == nil {
156+
fut.done = make(chan struct{})
157+
}
128158
return fut.done
129159
}

0 commit comments

Comments
 (0)