Skip to content

Commit 68b5b99

Browse files
authored
fix: OnRequest should wait all readable data consumed when sender close connection (#278)
1 parent 68ebb21 commit 68b5b99

15 files changed

+287
-159
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,23 @@ jobs:
3939

4040
steps:
4141
- name: Checkout repository
42-
uses: actions/checkout@v2
42+
uses: actions/checkout@v3
4343

4444
- name: Set up Go
4545
uses: actions/setup-go@v2
4646
with:
47-
go-version: 1.16
47+
go-version: "1.20"
4848

49-
- uses: actions/cache@v2
50-
with:
51-
path: ~/go/pkg/mod
52-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
53-
restore-keys: |
54-
${{ runner.os }}-go-
49+
# - uses: actions/cache@v2
50+
# with:
51+
# path: ~/go/pkg/mod
52+
# key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
53+
# restore-keys: |
54+
# ${{ runner.os }}-go-
5555

5656
# Initializes the CodeQL tools for scanning.
5757
- name: Initialize CodeQL
58-
uses: github/codeql-action/init@v1
58+
uses: github/codeql-action/init@v2
5959
with:
6060
languages: ${{ matrix.language }}
6161
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -66,7 +66,7 @@ jobs:
6666
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
6767
# If this step fails, then you should remove it and run the build manually (see below)
6868
- name: Autobuild
69-
uses: github/codeql-action/autobuild@v1
69+
uses: github/codeql-action/autobuild@v2
7070

7171
# ℹ️ Command-line programs to run using the OS shell.
7272
# 📚 https://git.io/JvXDl
@@ -80,4 +80,4 @@ jobs:
8080
# make release
8181

8282
- name: Perform CodeQL Analysis
83-
uses: github/codeql-action/analyze@v1
83+
uses: github/codeql-action/analyze@v2

.github/workflows/pr-check.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Push and Pull Request Check
22

3-
on: [ push, pull_request ]
3+
on: [ push ]
44

55
jobs:
66
compatibility-test:
@@ -15,12 +15,12 @@ jobs:
1515
uses: actions/setup-go@v3
1616
with:
1717
go-version: ${{ matrix.go }}
18-
- uses: actions/cache@v2
19-
with:
20-
path: ~/go/pkg/mod
21-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
22-
restore-keys: |
23-
${{ runner.os }}-go-
18+
# - uses: actions/cache@v2
19+
# with:
20+
# path: ~/go/pkg/mod
21+
# key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
22+
# restore-keys: |
23+
# ${{ runner.os }}-go-
2424
- name: Unit Test
2525
run: go test -v -race -covermode=atomic -coverprofile=coverage.out ./...
2626
- name: Benchmark
@@ -33,12 +33,12 @@ jobs:
3333
uses: actions/setup-go@v3
3434
with:
3535
go-version: "1.20"
36-
- uses: actions/cache@v2
37-
with:
38-
path: ~/go/pkg/mod
39-
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
40-
restore-keys: |
41-
${{ runner.os }}-go-
36+
# - uses: actions/cache@v2
37+
# with:
38+
# path: ~/go/pkg/mod
39+
# key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
40+
# restore-keys: |
41+
# ${{ runner.os }}-go-
4242
- name: Build Test
4343
run: go vet -v ./...
4444
style-test:

connection_impl.go

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type connection struct {
3636
operator *FDOperator
3737
readTimeout time.Duration
3838
readTimer *time.Timer
39-
readTrigger chan struct{}
39+
readTrigger chan error
4040
waitReadSize int64
4141
writeTimeout time.Duration
4242
writeTimer *time.Timer
@@ -319,7 +319,7 @@ var barrierPool = sync.Pool{
319319
// init initialize the connection with options
320320
func (c *connection) init(conn Conn, opts *options) (err error) {
321321
// init buffer, barrier, finalizer
322-
c.readTrigger = make(chan struct{}, 1)
322+
c.readTrigger = make(chan error, 1)
323323
c.writeTrigger = make(chan error, 1)
324324
c.bookSize, c.maxSize = pagesize, pagesize
325325
c.inputBuffer, c.outputBuffer = NewLinkBuffer(pagesize), NewLinkBuffer()
@@ -357,19 +357,12 @@ func (c *connection) initNetFD(conn Conn) {
357357
}
358358

359359
func (c *connection) initFDOperator() {
360-
var op *FDOperator
361-
if c.pd != nil && c.pd.operator != nil {
362-
// reuse operator created at connect step
363-
op = c.pd.operator
364-
} else {
365-
poll := pollmanager.Pick()
366-
op = poll.Alloc()
367-
}
360+
poll := pollmanager.Pick()
361+
op := poll.Alloc()
368362
op.FD = c.fd
369363
op.OnRead, op.OnWrite, op.OnHup = nil, nil, c.onHup
370364
op.Inputs, op.InputAck = c.inputs, c.inputAck
371365
op.Outputs, op.OutputAck = c.outputs, c.outputAck
372-
373366
c.operator = op
374367
}
375368

@@ -385,9 +378,9 @@ func (c *connection) initFinalizer() {
385378
})
386379
}
387380

388-
func (c *connection) triggerRead() {
381+
func (c *connection) triggerRead(err error) {
389382
select {
390-
case c.readTrigger <- struct{}{}:
383+
case c.readTrigger <- err:
391384
default:
392385
}
393386
}
@@ -411,10 +404,17 @@ func (c *connection) waitRead(n int) (err error) {
411404
}
412405
// wait full n
413406
for c.inputBuffer.Len() < n {
414-
if !c.IsActive() {
407+
switch c.status(closing) {
408+
case poller:
409+
return Exception(ErrEOF, "wait read")
410+
case user:
415411
return Exception(ErrConnClosed, "wait read")
412+
default:
413+
err = <-c.readTrigger
414+
if err != nil {
415+
return err
416+
}
416417
}
417-
<-c.readTrigger
418418
}
419419
return nil
420420
}
@@ -429,24 +429,32 @@ func (c *connection) waitReadWithTimeout(n int) (err error) {
429429
}
430430

431431
for c.inputBuffer.Len() < n {
432-
if !c.IsActive() {
433-
// cannot return directly, stop timer before !
432+
switch c.status(closing) {
433+
case poller:
434+
// cannot return directly, stop timer first!
435+
err = Exception(ErrEOF, "wait read")
436+
goto RET
437+
case user:
438+
// cannot return directly, stop timer first!
434439
err = Exception(ErrConnClosed, "wait read")
435-
break
436-
}
437-
438-
select {
439-
case <-c.readTimer.C:
440-
// double check if there is enough data to be read
441-
if c.inputBuffer.Len() >= n {
442-
return nil
440+
goto RET
441+
default:
442+
select {
443+
case <-c.readTimer.C:
444+
// double check if there is enough data to be read
445+
if c.inputBuffer.Len() >= n {
446+
return nil
447+
}
448+
return Exception(ErrReadTimeout, c.remoteAddr.String())
449+
case err = <-c.readTrigger:
450+
if err != nil {
451+
return err
452+
}
453+
continue
443454
}
444-
return Exception(ErrReadTimeout, c.remoteAddr.String())
445-
case <-c.readTrigger:
446-
continue
447455
}
448456
}
449-
457+
RET:
450458
// clean timer.C
451459
if !c.readTimer.Stop() {
452460
<-c.readTimer.C

connection_lock.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"sync/atomic"
2020
)
2121

22-
type who int32
22+
type who = int32
2323

2424
const (
2525
none who = iota
@@ -65,6 +65,14 @@ func (l *locker) isCloseBy(w who) (yes bool) {
6565
return atomic.LoadInt32(&l.keychain[closing]) == int32(w)
6666
}
6767

68+
func (l *locker) status(k key) int32 {
69+
return atomic.LoadInt32(&l.keychain[k])
70+
}
71+
72+
func (l *locker) force(k key, v int32) {
73+
atomic.StoreInt32(&l.keychain[k], v)
74+
}
75+
6876
func (l *locker) lock(k key) (success bool) {
6977
return atomic.CompareAndSwapInt32(&l.keychain[k], 0, 1)
7078
}

connection_onevent.go

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func (c *connection) onProcess(isProcessable func(c *connection) bool, process f
195195
if isProcessable(c) {
196196
process(c)
197197
}
198-
for c.IsActive() && isProcessable(c) {
198+
for !c.isCloseBy(user) && isProcessable(c) {
199199
process(c)
200200
}
201201
// Handling callback if connection has been closed.
@@ -225,12 +225,6 @@ func (c *connection) closeCallback(needLock bool) (err error) {
225225
if needLock && !c.lock(processing) {
226226
return nil
227227
}
228-
// If Close is called during OnPrepare, poll is not registered.
229-
if c.isCloseBy(user) && c.operator.poll != nil {
230-
if err = c.operator.Control(PollDetach); err != nil {
231-
logger.Printf("NETPOLL: closeCallback detach operator failed: %v", err)
232-
}
233-
}
234228
var latest = c.closeCallbacks.Load()
235229
if latest == nil {
236230
return nil
@@ -243,14 +237,7 @@ func (c *connection) closeCallback(needLock bool) (err error) {
243237

244238
// register only use for connection register into poll.
245239
func (c *connection) register() (err error) {
246-
if c.operator.isUnused() {
247-
// operator is not registered
248-
err = c.operator.Control(PollReadable)
249-
} else {
250-
// operator is already registered
251-
// change event to wait read new data
252-
err = c.operator.Control(PollModReadable)
253-
}
240+
err = c.operator.Control(PollReadable)
254241
if err != nil {
255242
logger.Printf("NETPOLL: connection register failed: %v", err)
256243
c.Close()

connection_reactor.go

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,44 @@ import (
2525

2626
// onHup means close by poller.
2727
func (c *connection) onHup(p Poll) error {
28-
if c.closeBy(poller) {
29-
c.triggerRead()
30-
c.triggerWrite(ErrConnClosed)
31-
// It depends on closing by user if OnConnect and OnRequest is nil, otherwise it needs to be released actively.
32-
// It can be confirmed that the OnRequest goroutine has been exited before closecallback executing,
33-
// and it is safe to close the buffer at this time.
34-
var onConnect, _ = c.onConnectCallback.Load().(OnConnect)
35-
var onRequest, _ = c.onRequestCallback.Load().(OnRequest)
36-
if onConnect != nil || onRequest != nil {
37-
c.closeCallback(true)
38-
}
28+
if !c.closeBy(poller) {
29+
return nil
30+
}
31+
// already PollDetach when call OnHup
32+
c.triggerRead(Exception(ErrEOF, "peer close"))
33+
c.triggerWrite(Exception(ErrConnClosed, "peer close"))
34+
// It depends on closing by user if OnConnect and OnRequest is nil, otherwise it needs to be released actively.
35+
// It can be confirmed that the OnRequest goroutine has been exited before closecallback executing,
36+
// and it is safe to close the buffer at this time.
37+
var onConnect, _ = c.onConnectCallback.Load().(OnConnect)
38+
var onRequest, _ = c.onRequestCallback.Load().(OnRequest)
39+
if onConnect != nil || onRequest != nil {
40+
c.closeCallback(true)
3941
}
4042
return nil
4143
}
4244

4345
// onClose means close by user.
4446
func (c *connection) onClose() error {
4547
if c.closeBy(user) {
46-
c.triggerRead()
47-
c.triggerWrite(ErrConnClosed)
48+
// If Close is called during OnPrepare, poll is not registered.
49+
if c.operator.poll != nil {
50+
if err := c.operator.Control(PollDetach); err != nil {
51+
logger.Printf("NETPOLL: onClose detach operator failed: %v", err)
52+
}
53+
}
54+
c.triggerRead(Exception(ErrConnClosed, "self close"))
55+
c.triggerWrite(Exception(ErrConnClosed, "self close"))
4856
c.closeCallback(true)
4957
return nil
5058
}
51-
if c.isCloseBy(poller) {
52-
// Connection with OnRequest of nil
53-
// relies on the user to actively close the connection to recycle resources.
59+
60+
closedByPoller := c.isCloseBy(poller)
61+
// force change closed by user
62+
c.force(closing, user)
63+
64+
// If OnRequest is nil, relies on the user to actively close the connection to recycle resources.
65+
if closedByPoller {
5466
c.closeCallback(true)
5567
}
5668
return nil
@@ -103,7 +115,7 @@ func (c *connection) inputAck(n int) (err error) {
103115
needTrigger = c.onRequest()
104116
}
105117
if needTrigger && length >= int(atomic.LoadInt64(&c.waitReadSize)) {
106-
c.triggerRead()
118+
c.triggerRead(nil)
107119
}
108120
return nil
109121
}

0 commit comments

Comments
 (0)