Skip to content

Commit da9ec89

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 (bool) Other code use `Future.finished` 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 da9ec89

File tree

4 files changed

+72
-31
lines changed

4 files changed

+72
-31
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: 20 additions & 11 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,12 +1007,12 @@ 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

10141014
select {
1015-
case <-fut.done:
1015+
case <-fut.WaitChan():
10161016
return
10171017
default:
10181018
conn.cancelFuture(fut, fmt.Errorf("context is done (request ID %d): %w",
@@ -1034,7 +1034,12 @@ func (conn *Connection) send(req Request, streamId uint64) *Future {
10341034
conn.incrementRequestCnt()
10351035

10361036
fut := conn.newFuture(req)
1037-
if fut.done == nil {
1037+
1038+
fut.mutex.Lock()
1039+
is_done := fut.finished
1040+
fut.mutex.Unlock()
1041+
1042+
if is_done {
10381043
conn.decrementRequestCnt()
10391044
return fut
10401045
}
@@ -1057,12 +1062,16 @@ func (conn *Connection) putFuture(fut *Future, req Request, streamId uint64) {
10571062
shardn := fut.requestId & (conn.opts.Concurrency - 1)
10581063
shard := &conn.shard[shardn]
10591064
shard.bufmut.Lock()
1060-
select {
1061-
case <-fut.done:
1065+
1066+
fut.mutex.Lock()
1067+
is_done := fut.finished
1068+
fut.mutex.Unlock()
1069+
1070+
if is_done {
10621071
shard.bufmut.Unlock()
10631072
return
1064-
default:
10651073
}
1074+
10661075
firstWritten := shard.buf.Len() == 0
10671076
if shard.buf.Cap() == 0 {
10681077
shard.buf.b = make([]byte, 0, 128)

future.go

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,39 @@ type Future struct {
1515
mutex sync.Mutex
1616
resp Response
1717
err error
18+
cond sync.Cond
19+
finished bool
1820
done chan struct{}
1921
}
2022

2123
func (fut *Future) wait() {
22-
if fut.done == nil {
23-
return
24+
fut.mutex.Lock()
25+
defer fut.mutex.Unlock()
26+
27+
for !fut.finished {
28+
fut.cond.Wait()
2429
}
25-
<-fut.done
2630
}
2731

28-
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
32+
func (fut *Future) finish() {
33+
fut.mutex.Lock()
34+
defer fut.mutex.Unlock()
35+
36+
fut.finished = true
37+
38+
if fut.done != nil {
39+
close(fut.done)
3740
}
41+
42+
fut.cond.Broadcast()
3843
}
3944

4045
// NewFuture creates a new empty Future for a given Request.
4146
func NewFuture(req Request) (fut *Future) {
4247
fut = &Future{}
43-
fut.done = make(chan struct{})
48+
fut.done = nil
49+
fut.finished = false
50+
fut.cond.L = &fut.mutex
4451
fut.req = req
4552
return fut
4653
}
@@ -50,7 +57,7 @@ func (fut *Future) SetResponse(header Header, body io.Reader) error {
5057
fut.mutex.Lock()
5158
defer fut.mutex.Unlock()
5259

53-
if fut.isDone() {
60+
if fut.finished {
5461
return nil
5562
}
5663

@@ -60,7 +67,14 @@ func (fut *Future) SetResponse(header Header, body io.Reader) error {
6067
}
6168
fut.resp = resp
6269

63-
close(fut.done)
70+
fut.finished = true
71+
72+
if fut.done != nil {
73+
close(fut.done)
74+
}
75+
76+
fut.cond.Broadcast()
77+
6478
return nil
6579
}
6680

@@ -69,12 +83,18 @@ func (fut *Future) SetError(err error) {
6983
fut.mutex.Lock()
7084
defer fut.mutex.Unlock()
7185

72-
if fut.isDone() {
86+
if fut.finished {
7387
return
7488
}
7589
fut.err = err
7690

77-
close(fut.done)
91+
fut.finished = true
92+
93+
if fut.done != nil {
94+
close(fut.done)
95+
}
96+
97+
fut.cond.Broadcast()
7898
}
7999

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

123143
// WaitChan returns channel which becomes closed when response arrived or error occurred.
124144
func (fut *Future) WaitChan() <-chan struct{} {
125-
if fut.done == nil {
145+
fut.mutex.Lock()
146+
defer fut.mutex.Unlock()
147+
148+
if fut.finished {
126149
return closedChan
127150
}
151+
152+
if fut.done == nil {
153+
fut.done = make(chan struct{})
154+
}
155+
128156
return fut.done
129157
}

0 commit comments

Comments
 (0)