Skip to content

Commit c60525e

Browse files
committed
Optimize for performance
1 parent 77056df commit c60525e

19 files changed

+501
-286
lines changed

Diff for: bootstrap.go

+17-15
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type Bootstrap interface {
3030
// Context return context
3131
Context() context.Context
3232
// Listen create a listener
33-
Listen(url string, option ...transport.Option) Listener
33+
Listen(url string, attachment Attachment, option ...transport.Option) Listener
3434
// Connect to remote endpoint
3535
Connect(url string, attachment Attachment, option ...transport.Option) (Channel, error)
3636
// Shutdown boostrap
@@ -43,8 +43,9 @@ func NewBootstrap(option ...Option) Bootstrap {
4343
opts := &bootstrapOptions{
4444
channelIDFactory: SequenceID(),
4545
pipelineFactory: NewPipeline,
46-
channelFactory: NewChannel(128),
46+
channelFactory: NewChannel(64),
4747
transportFactory: tcp.New(),
48+
executor: AsyncExecutor(),
4849
}
4950
opts.bootstrapCtx, opts.bootstrapCancel = context.WithCancel(context.Background())
5051

@@ -67,7 +68,7 @@ func (bs *bootstrap) Context() context.Context {
6768
}
6869

6970
// serveTransport to serve channel
70-
func (bs *bootstrap) serveTransport(transport transport.Transport, attachment Attachment, childChannel bool) Channel {
71+
func (bs *bootstrap) serveTransport(ctx context.Context, transport transport.Transport, attachment Attachment, childChannel bool) Channel {
7172

7273
// create a new pipeline
7374
pl := bs.pipelineFactory()
@@ -76,7 +77,7 @@ func (bs *bootstrap) serveTransport(transport transport.Transport, attachment At
7677
cid := bs.channelIDFactory()
7778

7879
// create a channel
79-
ch := bs.channelFactory(cid, bs.bootstrapCtx, pl, transport)
80+
ch := bs.channelFactory(cid, ctx, pl, transport, bs.executor)
8081

8182
// set the attachment if necessary
8283
if nil != attachment {
@@ -110,12 +111,12 @@ func (bs *bootstrap) Connect(url string, attachment Attachment, option ...transp
110111
}
111112

112113
// serve client transport
113-
return bs.serveTransport(t, attachment, false), nil
114+
return bs.serveTransport(options.Context, t, attachment, false), nil
114115
}
115116

116117
// Listen to the address with options
117-
func (bs *bootstrap) Listen(url string, option ...transport.Option) Listener {
118-
l := &listener{bs: bs, url: url, option: option}
118+
func (bs *bootstrap) Listen(url string, attachment Attachment, option ...transport.Option) Listener {
119+
l := &listener{bs: bs, url: url, attachment: attachment, option: option}
119120
bs.listeners.Store(url, l)
120121
return l
121122
}
@@ -146,11 +147,12 @@ type Listener interface {
146147

147148
// impl Listener
148149
type listener struct {
149-
bs *bootstrap
150-
url string
151-
option []transport.Option
152-
options *transport.Options
153-
acceptor transport.Acceptor
150+
bs *bootstrap
151+
url string
152+
attachment Attachment
153+
option []transport.Option
154+
options *transport.Options
155+
acceptor transport.Acceptor
154156
}
155157

156158
// Close listener
@@ -191,14 +193,14 @@ func (l *listener) Sync() error {
191193
return t.Close()
192194
default:
193195
// serve child transport
194-
l.bs.serveTransport(t, nil, true)
196+
l.bs.serveTransport(l.options.Context, t, l.attachment, true)
195197
}
196198
}
197199
}
198200

199201
// Async accept new transport from listener
200202
func (l *listener) Async(fn func(err error)) {
201-
go func() {
203+
l.bs.executor.Exec(func() {
202204
fn(l.Sync())
203-
}()
205+
})
204206
}

Diff for: bootstrap_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ func TestBootstrap(t *testing.T) {
5050
}
5151

5252
bs := NewBootstrap(
53-
WithChannel(NewBufferedChannel(128, 1024)),
5453
WithChildInitializer(pipelineInitializer),
5554
WithClientInitializer(pipelineInitializer),
5655
WithTransport(tcp.New()),

Diff for: channel.go

+100-96
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"context"
2121
"errors"
2222
"net"
23-
"runtime/debug"
2423
"sync"
2524
"sync/atomic"
2625

@@ -34,7 +33,7 @@ type Channel interface {
3433
ID() int64
3534

3635
// Write a message through the Pipeline
37-
Write(Message) bool
36+
Write(Message) error
3837

3938
// Trigger user event
4039
Trigger(event Event)
@@ -75,43 +74,46 @@ type Channel interface {
7574

7675
// NewChannel create a ChannelFactory
7776
func NewChannel(capacity int) ChannelFactory {
78-
return func(id int64, ctx context.Context, pipeline Pipeline, transport transport.Transport) Channel {
79-
return newChannelWith(ctx, pipeline, transport, id, capacity)
80-
}
81-
}
82-
83-
// NewBufferedChannel create a ChannelFactory with buffered transport
84-
func NewBufferedChannel(capacity int, sizeRead int) ChannelFactory {
85-
return func(id int64, ctx context.Context, pipeline Pipeline, tran transport.Transport) Channel {
86-
tran = transport.BufferedTransport(tran, sizeRead)
87-
return newChannelWith(ctx, pipeline, tran, id, capacity)
77+
return func(id int64, ctx context.Context, pipeline Pipeline, transport transport.Transport, executor Executor) Channel {
78+
return newChannelWith(ctx, pipeline, transport, executor, id, capacity)
8879
}
8980
}
9081

9182
// newChannelWith internal method for NewChannel & NewBufferedChannel
92-
func newChannelWith(ctx context.Context, pipeline Pipeline, transport transport.Transport, id int64, capacity int) Channel {
83+
func newChannelWith(ctx context.Context, pipeline Pipeline, transport transport.Transport, executor Executor, id int64, capacity int) Channel {
9384
childCtx, cancel := context.WithCancel(ctx)
9485
return &channel{
95-
id: id,
96-
ctx: childCtx,
97-
cancel: cancel,
98-
pipeline: pipeline,
99-
transport: transport,
100-
sendQueue: make(chan [][]byte, capacity),
86+
id: id,
87+
ctx: childCtx,
88+
cancel: cancel,
89+
pipeline: pipeline,
90+
transport: transport,
91+
executor: executor,
92+
sendQueue: utils.NewRingBuffer(uint64(capacity)),
93+
writeBuffers: make(net.Buffers, 0, capacity/3+8),
94+
writeIndexes: make([]int, 0, capacity/3+8),
10195
}
10296
}
10397

98+
const idle = 0
99+
const running = 1
100+
104101
// implement of Channel
105102
type channel struct {
106-
id int64
107-
ctx context.Context
108-
cancel context.CancelFunc
109-
transport transport.Transport
110-
pipeline Pipeline
111-
attachment Attachment
112-
sendQueue chan [][]byte
113-
activeWait sync.WaitGroup
114-
closed int32
103+
id int64
104+
ctx context.Context
105+
cancel context.CancelFunc
106+
transport transport.Transport
107+
executor Executor
108+
pipeline Pipeline
109+
attachment Attachment
110+
sendQueue *utils.RingBuffer
111+
writeBuffers net.Buffers
112+
writeIndexes []int
113+
activeWait sync.WaitGroup
114+
closed int32
115+
running int32
116+
closeErr error
115117
}
116118

117119
// ID get channel id
@@ -120,22 +122,22 @@ func (c *channel) ID() int64 {
120122
}
121123

122124
// Write a message through the Pipeline
123-
func (c *channel) Write(message Message) bool {
124-
125-
select {
126-
case <-c.ctx.Done():
127-
return false
128-
default:
129-
c.invokeMethod(func() {
130-
c.pipeline.FireChannelWrite(message)
131-
})
132-
return true
125+
func (c *channel) Write(message Message) error {
126+
if !c.IsActive() {
127+
select {
128+
case <-c.ctx.Done():
129+
return c.closeErr
130+
}
133131
}
132+
133+
c.invokeMethod(func() {
134+
c.pipeline.FireChannelWrite(message)
135+
})
136+
return nil
134137
}
135138

136139
// Trigger trigger event
137140
func (c *channel) Trigger(event Event) {
138-
139141
c.invokeMethod(func() {
140142
c.pipeline.FireChannelEvent(event)
141143
})
@@ -144,27 +146,34 @@ func (c *channel) Trigger(event Event) {
144146
// Close through the Pipeline
145147
func (c *channel) Close(err error) {
146148
if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
147-
c.cancel()
149+
c.closeErr = err
150+
c.sendQueue.Dispose()
148151
c.transport.Close()
152+
c.cancel()
149153

150154
c.invokeMethod(func() {
151-
c.pipeline.FireChannelInactive(AsException(err, debug.Stack()))
155+
c.pipeline.FireChannelInactive(err)
152156
})
153157
}
154158
}
155159

156160
// Writev to write [][]byte for optimize syscall
157161
func (c *channel) Writev(p [][]byte) (n int64, err error) {
158-
159-
select {
160-
case <-c.ctx.Done():
161-
return 0, errors.New("broken pipe")
162-
case c.sendQueue <- p:
163-
for _, d := range p {
164-
n += int64(len(d))
162+
if !c.IsActive() {
163+
select {
164+
case <-c.ctx.Done():
165+
return 0, c.closeErr
165166
}
166-
return
167167
}
168+
// put packet to send queue
169+
if err = c.sendQueue.Put(p); nil != err {
170+
return 0, err
171+
}
172+
// try send
173+
if atomic.CompareAndSwapInt32(&c.running, idle, running) {
174+
c.executor.Exec(c.writeOnce)
175+
}
176+
return utils.CountOf(p), nil
168177
}
169178

170179
// IsActive return true if the Channel is active and so connected
@@ -210,20 +219,19 @@ func (c *channel) Context() context.Context {
210219
// serveChannel start write & read routines
211220
func (c *channel) serveChannel() {
212221
c.activeWait.Add(1)
213-
go c.readLoop()
214-
go c.writeLoop()
222+
c.executor.Exec(c.readLoop)
215223
c.activeWait.Wait()
216224
}
217225

218226
func (c *channel) invokeMethod(fn func()) {
219227

220228
defer func() {
221229
if err := recover(); nil != err && 0 == atomic.LoadInt32(&c.closed) {
222-
c.pipeline.FireChannelException(AsException(err, debug.Stack()))
230+
c.pipeline.FireChannelException(AsException(err))
223231

224232
if e, ok := err.(error); ok {
225233
var ne net.Error
226-
if errors.As(e, &ne) && !ne.Temporary() {
234+
if errors.As(e, &ne) && !ne.Timeout() {
227235
c.Close(e)
228236
}
229237
}
@@ -237,11 +245,7 @@ func (c *channel) invokeMethod(fn func()) {
237245
func (c *channel) readLoop() {
238246

239247
defer func() {
240-
if err := recover(); nil != err {
241-
c.Close(AsException(err, debug.Stack()))
242-
} else {
243-
c.Close(nil)
244-
}
248+
c.Close(AsException(recover()))
245249
}()
246250

247251
func() {
@@ -261,56 +265,56 @@ func (c *channel) readLoop() {
261265
}
262266
}
263267

264-
// writeLoop sending message of channel
265-
func (c *channel) writeLoop() {
268+
// writeOnce sending messages of channel
269+
func (c *channel) writeOnce() {
266270

267271
defer func() {
268272
if err := recover(); nil != err {
269-
c.Close(AsException(err, debug.Stack()))
270-
} else {
271-
c.Close(nil)
273+
c.Close(AsException(err))
272274
}
273275
}()
274276

275-
var bufferCap = cap(c.sendQueue)
276-
var buffers = make(net.Buffers, 0, bufferCap)
277-
var indexes = make([]int, 0, bufferCap)
277+
for {
278+
// reuse buffer.
279+
sendBuffers := c.writeBuffers[:0]
280+
sendIndexes := c.writeIndexes[:0]
281+
282+
// more packet will be merged
283+
for c.sendQueue.Len() > 0 && len(sendBuffers) < cap(sendBuffers) {
284+
// poll packet
285+
item, err := c.sendQueue.Poll(-1)
286+
if nil != err {
287+
break
288+
}
278289

279-
// Try to combine packet sending to optimize sending performance
280-
sendWithWritev := func(data [][]byte, queue <-chan [][]byte) (int64, error) {
290+
// combine send bytes to reduce syscall.
291+
pkts := item.([][]byte)
292+
sendBuffers = append(sendBuffers, pkts...)
293+
sendIndexes = append(sendIndexes, len(sendBuffers))
294+
}
281295

282-
// reuse buffer.
283-
sendBuffers := buffers[:0]
284-
sendIndexes := indexes[:0]
285-
286-
// append first packet.
287-
sendBuffers = append(sendBuffers, data...)
288-
sendIndexes = append(sendIndexes, len(sendBuffers))
289-
290-
// more packet will be merged.
291-
for {
292-
select {
293-
case data := <-queue:
294-
sendBuffers = append(sendBuffers, data...)
295-
sendIndexes = append(sendIndexes, len(sendBuffers))
296-
if len(sendIndexes) >= bufferCap {
297-
return c.transport.Writev(transport.Buffers{Buffers: sendBuffers, Indexes: sendIndexes})
296+
if len(sendBuffers) > 0 {
297+
utils.AssertLong(c.transport.Writev(transport.Buffers{Buffers: sendBuffers, Indexes: sendIndexes}))
298+
utils.Assert(c.transport.Flush())
299+
300+
// clear buffer ref
301+
for index := range sendBuffers {
302+
sendBuffers[index] = nil // avoid memory leak
303+
if index < len(sendIndexes) {
304+
sendIndexes[index] = -1 // for safety
298305
}
299-
default:
300-
return c.transport.Writev(transport.Buffers{Buffers: sendBuffers, Indexes: sendIndexes})
301306
}
302307
}
303-
}
304308

305-
for {
306-
select {
307-
case buf := <-c.sendQueue:
308-
// combine send bytes to reduce syscall.
309-
utils.AssertLong(sendWithWritev(buf, c.sendQueue))
310-
// flush buffer
311-
utils.Assert(c.transport.Flush())
312-
case <-c.ctx.Done():
313-
return
309+
// double check
310+
atomic.StoreInt32(&c.running, idle)
311+
if size := c.sendQueue.Len(); size > 0 {
312+
if atomic.CompareAndSwapInt32(&c.running, idle, running) {
313+
continue
314+
}
314315
}
316+
317+
// no packets to send
318+
break
315319
}
316320
}

0 commit comments

Comments
 (0)