@@ -32,35 +32,248 @@ type BatchPacketConn interface {
3232 io.Closer
3333}
3434
35+ // ---------------------------------
36+
37+ type messageBatch struct {
38+ size int
39+ batchConn BatchPacketConn
40+
41+ mu sync.Mutex
42+ messages []ipv4.Message
43+ writePos int
44+ }
45+
46+ func newMessageBatch (size int , batchConn BatchPacketConn ) * messageBatch {
47+ m := & messageBatch {
48+ size : size ,
49+ batchConn : batchConn ,
50+ }
51+ m .init ()
52+
53+ return m
54+ }
55+
56+ func (m * messageBatch ) init () {
57+ m .messages = make ([]ipv4.Message , m .size )
58+ for i := range m .messages {
59+ m .messages [i ].Buffers = [][]byte {make ([]byte , sendMTU )}
60+ }
61+ }
62+
63+ func (m * messageBatch ) isFull () bool {
64+ return m .writePos == m .size
65+ }
66+
67+ func (m * messageBatch ) EnqueueMessage (buf []byte , raddr net.Addr ) (int , bool ) {
68+ m .mu .Lock ()
69+ defer m .mu .Unlock ()
70+
71+ if len (buf ) == 0 || m .isFull () {
72+ return 0 , m .isFull ()
73+ }
74+
75+ msg := & m .messages [m .writePos ]
76+ // reset buffers
77+ msg .Buffers = msg .Buffers [:1 ]
78+ msg .Buffers [0 ] = msg .Buffers [0 ][:cap (msg .Buffers [0 ])]
79+
80+ if raddr != nil {
81+ msg .Addr = raddr
82+ }
83+ if n := copy (msg .Buffers [0 ], buf ); n < len (buf ) {
84+ extraBuffer := make ([]byte , len (buf )- n )
85+ copy (extraBuffer , buf [n :])
86+ msg .Buffers = append (msg .Buffers , extraBuffer )
87+ } else {
88+ msg .Buffers [0 ] = msg .Buffers [0 ][:n ]
89+ }
90+ m .writePos ++
91+
92+ return len (buf ), m .isFull ()
93+ }
94+
95+ func (m * messageBatch ) Flush () {
96+ m .mu .Lock ()
97+ defer m .mu .Unlock ()
98+
99+ var txN int
100+ for txN < m .writePos {
101+ n , err := m .batchConn .WriteBatch (m .messages [txN :m .writePos ], 0 )
102+ if err != nil {
103+ break
104+ }
105+ txN += n
106+ }
107+
108+ m .writePos = 0
109+ }
110+
111+ // ---------------------------------
112+
113+ type pingPong struct {
114+ mu sync.Mutex
115+ batches [2 ]* messageBatch
116+ writeBatchIdx int
117+ readBatchIdx int
118+ flushPending bool
119+
120+ writeReady chan struct {}
121+ flushCycleDone chan struct {}
122+ flusherDone chan struct {}
123+
124+ closed int32
125+ }
126+
127+ func newPingPong (size int , interval time.Duration , batchConn BatchPacketConn ) * pingPong {
128+ p := & pingPong {
129+ writeReady : make (chan struct {}),
130+ flushCycleDone : make (chan struct {}),
131+ flusherDone : make (chan struct {}),
132+ }
133+ for i := 0 ; i < len (p .batches ); i ++ {
134+ p .batches [i ] = newMessageBatch (size , batchConn )
135+ }
136+
137+ go p .flusher (interval )
138+
139+ return p
140+ }
141+
142+ func (p * pingPong ) Close () {
143+ atomic .StoreInt32 (& p .closed , 1 )
144+
145+ select {
146+ case p .writeReady <- struct {}{}:
147+ default :
148+ }
149+
150+ <- p .flusherDone
151+ }
152+
153+ func (p * pingPong ) EnqueueMessage (buf []byte , raddr net.Addr ) int {
154+ p .mu .Lock ()
155+ var (
156+ writeBatch * messageBatch
157+ n int
158+ isFull bool
159+ )
160+ for {
161+ if writeBatch = p .getWriteBatch (); writeBatch != nil {
162+ n , isFull = writeBatch .EnqueueMessage (buf , raddr )
163+ if n == len (buf ) {
164+ break
165+ }
166+ }
167+
168+ p .mu .Unlock ()
169+ select {
170+ case <- p .flushCycleDone :
171+ case <- time .After (100 * time .Microsecond ):
172+ }
173+
174+ if atomic .LoadInt32 (& p .closed ) == 1 {
175+ return 0
176+ }
177+
178+ p .mu .Lock ()
179+ }
180+ p .mu .Unlock ()
181+
182+ // enqueuing given message fills up the write batch, queue up a flush
183+ if isFull {
184+ select {
185+ case p .writeReady <- struct {}{}:
186+ default :
187+ }
188+ }
189+
190+ return n
191+ }
192+
193+ func (p * pingPong ) getWriteBatch () * messageBatch {
194+ if p .writeBatchIdx == p .readBatchIdx && p .flushPending {
195+ return nil
196+ }
197+
198+ return p .batches [p .writeBatchIdx ]
199+ }
200+
201+ func (p * pingPong ) updateWriteBatchAndGetReadBatch () * messageBatch {
202+ p .mu .Lock ()
203+ defer p .mu .Unlock ()
204+
205+ if p .writeBatchIdx != p .readBatchIdx || p .flushPending {
206+ return nil
207+ }
208+
209+ p .writeBatchIdx ^= 1
210+ p .flushPending = true
211+
212+ return p .batches [p .readBatchIdx ]
213+ }
214+
215+ func (p * pingPong ) updateReadBatch () {
216+ p .mu .Lock ()
217+ defer p .mu .Unlock ()
218+
219+ if ! p .flushPending {
220+ return
221+ }
222+
223+ p .readBatchIdx ^= 1
224+ p .flushPending = false
225+
226+ select {
227+ case p .flushCycleDone <- struct {}{}:
228+ default :
229+ }
230+ }
231+
232+ func (p * pingPong ) flusher (interval time.Duration ) {
233+ defer close (p .flusherDone )
234+
235+ writeTicker := time .NewTicker (interval / 2 )
236+ defer writeTicker .Stop ()
237+
238+ lastFlushAt := time .Now ().Add (- interval )
239+ for atomic .LoadInt32 (& p .closed ) != 1 {
240+ select {
241+ case <- writeTicker .C :
242+ if time .Since (lastFlushAt ) < interval {
243+ continue
244+ }
245+ case <- p .writeReady :
246+ }
247+
248+ readBatch := p .updateWriteBatchAndGetReadBatch ()
249+ if readBatch == nil {
250+ continue
251+ }
252+
253+ readBatch .Flush ()
254+ p .updateReadBatch ()
255+
256+ lastFlushAt = time .Now ()
257+ }
258+ }
259+
260+ // ------------------------------
261+
35262// BatchConn uses ipv4/v6.NewPacketConn to wrap a net.PacketConn to write/read messages in batch,
36263// only available in linux. In other platform, it will use single Write/Read as same as net.Conn.
37264type BatchConn struct {
38265 net.PacketConn
39266
40267 batchConn BatchPacketConn
41268
42- batchWriteMutex sync.Mutex
43- batchWriteMessages []ipv4.Message
44- batchWritePos int
45- batchWriteLast time.Time
46-
47- batchWriteSize int
48- batchWriteInterval time.Duration
49-
50- closed int32
269+ // ping-pong the batches to be able to accept new packets while a batch is written to socket
270+ batchPingPong * pingPong
51271}
52272
53273// NewBatchConn creates a *BatchConn from net.PacketConn with batch configs.
54274func NewBatchConn (conn net.PacketConn , batchWriteSize int , batchWriteInterval time.Duration ) * BatchConn {
55275 bc := & BatchConn {
56- PacketConn : conn ,
57- batchWriteLast : time .Now (),
58- batchWriteInterval : batchWriteInterval ,
59- batchWriteSize : batchWriteSize ,
60- batchWriteMessages : make ([]ipv4.Message , batchWriteSize ),
61- }
62- for i := range bc .batchWriteMessages {
63- bc .batchWriteMessages [i ].Buffers = [][]byte {make ([]byte , sendMTU )}
276+ PacketConn : conn ,
64277 }
65278
66279 // batch write only supports linux
@@ -70,34 +283,19 @@ func NewBatchConn(conn net.PacketConn, batchWriteSize int, batchWriteInterval ti
70283 } else if pc6 := ipv6 .NewPacketConn (conn ); pc6 != nil {
71284 bc .batchConn = pc6
72285 }
73- }
74286
75- if bc .batchConn != nil {
76- go func () {
77- writeTicker := time .NewTicker (batchWriteInterval / 2 )
78- defer writeTicker .Stop ()
79- for atomic .LoadInt32 (& bc .closed ) != 1 {
80- <- writeTicker .C
81- bc .batchWriteMutex .Lock ()
82- if bc .batchWritePos > 0 && time .Since (bc .batchWriteLast ) >= bc .batchWriteInterval {
83- _ = bc .flush ()
84- }
85- bc .batchWriteMutex .Unlock ()
86- }
87- }()
287+ bc .batchPingPong = newPingPong (batchWriteSize , batchWriteInterval , bc .batchConn )
88288 }
89289
90290 return bc
91291}
92292
93293// Close batchConn and the underlying PacketConn.
94294func (c * BatchConn ) Close () error {
95- atomic .StoreInt32 (& c .closed , 1 )
96- c .batchWriteMutex .Lock ()
97- if c .batchWritePos > 0 {
98- _ = c .flush ()
295+ if c .batchPingPong != nil {
296+ c .batchPingPong .Close ()
99297 }
100- c . batchWriteMutex . Unlock ()
298+
101299 if c .batchConn != nil {
102300 return c .batchConn .Close ()
103301 }
@@ -111,35 +309,7 @@ func (c *BatchConn) WriteTo(b []byte, addr net.Addr) (int, error) {
111309 return c .PacketConn .WriteTo (b , addr )
112310 }
113311
114- return c .enqueueMessage (b , addr )
115- }
116-
117- func (c * BatchConn ) enqueueMessage (buf []byte , raddr net.Addr ) (int , error ) {
118- var err error
119- c .batchWriteMutex .Lock ()
120- defer c .batchWriteMutex .Unlock ()
121-
122- msg := & c .batchWriteMessages [c .batchWritePos ]
123- // reset buffers
124- msg .Buffers = msg .Buffers [:1 ]
125- msg .Buffers [0 ] = msg .Buffers [0 ][:cap (msg .Buffers [0 ])]
126-
127- c .batchWritePos ++
128- if raddr != nil {
129- msg .Addr = raddr
130- }
131- if n := copy (msg .Buffers [0 ], buf ); n < len (buf ) {
132- extraBuffer := make ([]byte , len (buf )- n )
133- copy (extraBuffer , buf [n :])
134- msg .Buffers = append (msg .Buffers , extraBuffer )
135- } else {
136- msg .Buffers [0 ] = msg .Buffers [0 ][:n ]
137- }
138- if c .batchWritePos == c .batchWriteSize {
139- err = c .flush ()
140- }
141-
142- return len (buf ), err
312+ return c .batchPingPong .EnqueueMessage (b , addr ), nil
143313}
144314
145315// ReadBatch reads messages in batch, return length of message readed and error.
@@ -158,21 +328,3 @@ func (c *BatchConn) ReadBatch(msgs []ipv4.Message, flags int) (int, error) {
158328
159329 return c .batchConn .ReadBatch (msgs , flags )
160330}
161-
162- func (c * BatchConn ) flush () error {
163- var writeErr error
164- var txN int
165- for txN < c .batchWritePos {
166- n , err := c .batchConn .WriteBatch (c .batchWriteMessages [txN :c .batchWritePos ], 0 )
167- if err != nil {
168- writeErr = err
169-
170- break
171- }
172- txN += n
173- }
174- c .batchWritePos = 0
175- c .batchWriteLast = time .Now ()
176-
177- return writeErr
178- }
0 commit comments