@@ -23,26 +23,26 @@ import (
23
23
"github.com/gobwas/ws/wsutil"
24
24
"github.com/jensneuse/abstractlogger"
25
25
"github.com/wundergraph/graphql-go-tools/v2/pkg/engine/resolve"
26
- "github.com/wundergraph/graphql-go-tools/v2/pkg/epoller "
26
+ "github.com/wundergraph/graphql-go-tools/v2/pkg/netpoll "
27
27
"go.uber.org/atomic"
28
28
)
29
29
30
30
const ackWaitTimeout = 30 * time .Second
31
31
32
- type epollState struct {
32
+ type netPollState struct {
33
33
// connections is a map of fd -> connection to keep track of all active connections
34
34
connections map [int ]* connection
35
35
hasConnections atomic.Bool
36
36
// triggers is a map of subscription id -> fd to easily look up the connection for a subscription id
37
37
triggers map [uint64 ]int
38
38
39
- // clientUnsubscribe is a channel to signal to the epoll run loop that a client needs to be unsubscribed
39
+ // clientUnsubscribe is a channel to signal to the netPoll run loop that a client needs to be unsubscribed
40
40
clientUnsubscribe chan uint64
41
- // addConn is a channel to signal to the epoll run loop that a new connection needs to be added
41
+ // addConn is a channel to signal to the netPoll run loop that a new connection needs to be added
42
42
addConn chan * connection
43
- // waitForEventsTicker is the ticker for the epoll run loop
43
+ // waitForEventsTicker is the ticker for the netPoll run loop
44
44
// it is used to prevent busy waiting and to limit the CPU usage
45
- // instead of polling the epoll instance all the time, we wait until the next tick to throttle the epoll loop
45
+ // instead of polling the netPoll instance all the time, we wait until the next tick to throttle the netPoll loop
46
46
waitForEventsTicker * time.Ticker
47
47
48
48
// waitForEventsTick is the channel to receive the tick from the waitForEventsTicker
@@ -65,9 +65,9 @@ type subscriptionClient struct {
65
65
66
66
readTimeout time.Duration
67
67
68
- epoll epoller .Poller
69
- epollConfig EpollConfiguration
70
- epollState * epollState
68
+ netPoll netpoll .Poller
69
+ netPollConfig NetPollConfiguration
70
+ netPollState * netPollState
71
71
}
72
72
73
73
func (c * subscriptionClient ) SubscribeAsync (ctx * resolve.Context , id uint64 , options GraphQLSubscriptionOptions , updater resolve.SubscriptionUpdater ) error {
@@ -85,12 +85,12 @@ func (c *subscriptionClient) SubscribeAsync(ctx *resolve.Context, id uint64, opt
85
85
}
86
86
87
87
func (c * subscriptionClient ) Unsubscribe (id uint64 ) {
88
- // if we don't have epoll , we don't have a channel consumer of the clientUnsubscribe channel
88
+ // if we don't have netPoll , we don't have a channel consumer of the clientUnsubscribe channel
89
89
// we have to return to prevent a deadlock
90
- if c .epoll == nil {
90
+ if c .netPoll == nil {
91
91
return
92
92
}
93
- c .epollState .clientUnsubscribe <- id
93
+ c .netPollState .clientUnsubscribe <- id
94
94
}
95
95
96
96
type InvalidWsSubprotocolError struct {
@@ -121,21 +121,23 @@ func WithReadTimeout(timeout time.Duration) Options {
121
121
}
122
122
}
123
123
124
- type EpollConfiguration struct {
125
- // Disable can be set to true to disable epoll
126
- Disable bool
127
- // BufferSize defines the size of the buffer for the epoll loop
124
+ type NetPollConfiguration struct {
125
+ // Enable can be set to true to enable netPoll
126
+ Enable bool
127
+ // BufferSize defines the size of the buffer for the netPoll loop
128
128
BufferSize int
129
- // WaitForNumEvents defines how many events are waited for in the epoll loop before TickInterval cancels the wait
129
+ // WaitForNumEvents defines how many events are waited for in the netPoll loop before TickInterval cancels the wait
130
130
WaitForNumEvents int
131
131
// MaxEventWorkers defines the parallelism of how many connections can be handled at the same time
132
132
// The higher the number, the more CPU is used.
133
133
MaxEventWorkers int
134
- // TickInterval defines the time between each epoll loop when WaitForNumEvents is not reached
134
+ // TickInterval defines the time between each netPoll loop when WaitForNumEvents is not reached
135
135
TickInterval time.Duration
136
136
}
137
137
138
- func (e * EpollConfiguration ) ApplyDefaults () {
138
+ func (e * NetPollConfiguration ) ApplyDefaults () {
139
+ e .Enable = true
140
+
139
141
if e .BufferSize == 0 {
140
142
e .BufferSize = 1024
141
143
}
@@ -150,17 +152,17 @@ func (e *EpollConfiguration) ApplyDefaults() {
150
152
}
151
153
}
152
154
153
- func WithEpollConfiguration (config EpollConfiguration ) Options {
155
+ func WithNetPollConfiguration (config NetPollConfiguration ) Options {
154
156
return func (options * opts ) {
155
- options .epollConfiguration = config
157
+ options .netPollConfiguration = config
156
158
}
157
159
}
158
160
159
161
type opts struct {
160
162
readTimeout time.Duration
161
163
log abstractlogger.Logger
162
164
onWsConnectionInitCallback * OnWsConnectionInitCallback
163
- epollConfiguration EpollConfiguration
165
+ netPollConfiguration NetPollConfiguration
164
166
}
165
167
166
168
// GraphQLSubscriptionClientFactory abstracts the way of creating a new GraphQLSubscriptionClient.
@@ -185,10 +187,13 @@ func NewGraphQLSubscriptionClient(httpClient, streamingClient *http.Client, engi
185
187
readTimeout : time .Millisecond * 100 ,
186
188
log : abstractlogger .NoopLogger ,
187
189
}
190
+
191
+ op .netPollConfiguration .ApplyDefaults ()
192
+
188
193
for _ , option := range options {
189
194
option (op )
190
195
}
191
- op . epollConfiguration . ApplyDefaults ()
196
+
192
197
client := & subscriptionClient {
193
198
httpClient : httpClient ,
194
199
streamingClient : streamingClient ,
@@ -201,27 +206,27 @@ func NewGraphQLSubscriptionClient(httpClient, streamingClient *http.Client, engi
201
206
},
202
207
},
203
208
onWsConnectionInitCallback : op .onWsConnectionInitCallback ,
204
- epollConfig : op .epollConfiguration ,
209
+ netPollConfig : op .netPollConfiguration ,
205
210
}
206
- if ! op .epollConfiguration . Disable {
207
- client .epollState = & epollState {
211
+ if op .netPollConfiguration . Enable {
212
+ client .netPollState = & netPollState {
208
213
connections : make (map [int ]* connection ),
209
214
triggers : make (map [uint64 ]int ),
210
- clientUnsubscribe : make (chan uint64 , op .epollConfiguration .BufferSize ),
211
- addConn : make (chan * connection , op .epollConfiguration .BufferSize ),
215
+ clientUnsubscribe : make (chan uint64 , op .netPollConfiguration .BufferSize ),
216
+ addConn : make (chan * connection , op .netPollConfiguration .BufferSize ),
212
217
// this is not needed, but we want to make it explicit that we're starting with nil as the tick channel
213
- // reading from nil channels blocks forever, which allows us to prevent the epoll loop from starting
218
+ // reading from nil channels blocks forever, which allows us to prevent the netPoll loop from starting
214
219
// once we add the first connection, we start the ticker and set the tick channel
215
220
// after the last connection is removed, we set the tick channel to nil again
216
221
// this way we can start and stop the epoll loop dynamically
217
222
waitForEventsTick : nil ,
218
223
}
219
224
220
- // ignore error is ok, it means that epoll is not supported, which is handled gracefully by the client
221
- epoll , _ := epoller .NewPoller (op .epollConfiguration .BufferSize , op .epollConfiguration .TickInterval )
222
- if epoll != nil {
223
- client .epoll = epoll
224
- go client .runEpoll (engineCtx )
225
+ // ignore error is ok, it means that netPoll is not supported, which is handled gracefully by the client
226
+ poller , _ := netpoll .NewPoller (op .netPollConfiguration .BufferSize , op .netPollConfiguration .TickInterval )
227
+ if poller != nil {
228
+ client .netPoll = poller
229
+ go client .runNetPoll (engineCtx )
225
230
}
226
231
}
227
232
return client
@@ -317,7 +322,7 @@ func (c *subscriptionClient) asyncSubscribeWS(requestContext, engineContext cont
317
322
return err
318
323
}
319
324
320
- if c .epoll == nil {
325
+ if c .netPoll == nil {
321
326
go func () {
322
327
err := conn .handler .StartBlocking ()
323
328
if err != nil && ! errors .Is (err , context .DeadlineExceeded ) && ! errors .Is (err , context .Canceled ) {
@@ -332,10 +337,10 @@ func (c *subscriptionClient) asyncSubscribeWS(requestContext, engineContext cont
332
337
return err
333
338
}
334
339
335
- fd := epoller .SocketFD (conn .conn )
340
+ fd := netpoll .SocketFD (conn .conn )
336
341
conn .id , conn .fd = id , fd
337
- // submit the connection to the epoll run loop
338
- c .epollState .addConn <- conn
342
+ // submit the connection to the netPoll run loop
343
+ c .netPollState .addConn <- conn
339
344
return nil
340
345
}
341
346
@@ -614,20 +619,20 @@ type connResult struct {
614
619
shouldClose bool
615
620
}
616
621
617
- func (c * subscriptionClient ) runEpoll (ctx context.Context ) {
622
+ func (c * subscriptionClient ) runNetPoll (ctx context.Context ) {
618
623
defer c .close ()
619
624
done := ctx .Done ()
620
625
// both handleConnCh and connResults are buffered channels with a size of WaitForNumEvents
621
626
// this is important because we submit all events before we start processing them
622
627
// and we start evaluating the results only after all events have been submitted
623
628
// this would not be possible with unbuffered channels
624
- handleConnCh := make (chan * connection , c .epollConfig .WaitForNumEvents )
625
- connResults := make (chan connResult , c .epollConfig .WaitForNumEvents )
629
+ handleConnCh := make (chan * connection , c .netPollConfig .WaitForNumEvents )
630
+ connResults := make (chan connResult , c .netPollConfig .WaitForNumEvents )
626
631
627
632
// Start workers to handle connection events
628
633
// MaxEventWorkers defines the parallelism of how many connections can be handled at the same time
629
634
// This is the critical number on how much CPU is used
630
- for i := 0 ; i < c .epollConfig .MaxEventWorkers ; i ++ {
635
+ for i := 0 ; i < c .netPollConfig .MaxEventWorkers ; i ++ {
631
636
go func () {
632
637
for {
633
638
select {
@@ -641,34 +646,34 @@ func (c *subscriptionClient) runEpoll(ctx context.Context) {
641
646
}()
642
647
}
643
648
644
- // This is the main epoll run loop
649
+ // This is the main netPoll run loop
645
650
// It's a single threaded event loop that reacts to several events, such as added connections, clients unsubscribing, etc.
646
651
for {
647
652
select {
648
- // if the engine context is done, we close the epoll loop
653
+ // if the engine context is done, we close the netPoll loop
649
654
case <- done :
650
655
return
651
- case conn := <- c .epollState .addConn :
656
+ case conn := <- c .netPollState .addConn :
652
657
c .handleAddConn (conn )
653
- case id := <- c .epollState .clientUnsubscribe :
658
+ case id := <- c .netPollState .clientUnsubscribe :
654
659
c .handleClientUnsubscribe (id )
655
- // while len(c.connections) == 0, this channel is nil, so we will never try to wait for epoll events
660
+ // while len(c.connections) == 0, this channel is nil, so we will never try to wait for netPoll events
656
661
// this is important to prevent busy waiting
657
662
// once we add the first connection, we start the ticker and set the tick channel
658
- // the ticker ensures that we don't poll the epoll instance all the time,
663
+ // the ticker ensures that we don't poll the netPoll instance all the time,
659
664
// but at most every TickInterval
660
- case <- c .epollState .waitForEventsTick :
661
- events , err := c .epoll .Wait (c .epollConfig .WaitForNumEvents )
665
+ case <- c .netPollState .waitForEventsTick :
666
+ events , err := c .netPoll .Wait (c .netPollConfig .WaitForNumEvents )
662
667
if err != nil {
663
- c .log .Error ("epoll .Wait" , abstractlogger .Error (err ))
668
+ c .log .Error ("netPoll .Wait" , abstractlogger .Error (err ))
664
669
continue
665
670
}
666
671
667
672
waitForEvents := len (events )
668
673
669
674
for i := range events {
670
- fd := epoller .SocketFD (events [i ])
671
- conn , ok := c .epollState .connections [fd ]
675
+ fd := netpoll .SocketFD (events [i ])
676
+ conn , ok := c .netPollState .connections [fd ]
672
677
if ! ok {
673
678
// Should never happen
674
679
panic (fmt .Sprintf ("connection with fd %d not found" , fd ))
@@ -696,9 +701,9 @@ func (c *subscriptionClient) runEpoll(ctx context.Context) {
696
701
}
697
702
// we decrease the number of events we're waiting for to eventually break the loop
698
703
waitForEvents --
699
- case conn := <- c .epollState .addConn :
704
+ case conn := <- c .netPollState .addConn :
700
705
c .handleAddConn (conn )
701
- case id := <- c .epollState .clientUnsubscribe :
706
+ case id := <- c .netPollState .clientUnsubscribe :
702
707
c .handleClientUnsubscribe (id )
703
708
case <- done :
704
709
return
@@ -709,74 +714,74 @@ func (c *subscriptionClient) runEpoll(ctx context.Context) {
709
714
}
710
715
711
716
func (c * subscriptionClient ) close () {
712
- defer c .log .Debug ("subscriptionClient.close" , abstractlogger .String ("reason" , "epoll closed by context" ))
713
- if c .epollState .waitForEventsTicker != nil {
714
- c .epollState .waitForEventsTicker .Stop ()
717
+ defer c .log .Debug ("subscriptionClient.close" , abstractlogger .String ("reason" , "netPoll closed by context" ))
718
+ if c .netPollState .waitForEventsTicker != nil {
719
+ c .netPollState .waitForEventsTicker .Stop ()
715
720
}
716
- for _ , conn := range c .epollState .connections {
717
- _ = c .epoll .Remove (conn .conn )
721
+ for _ , conn := range c .netPollState .connections {
722
+ _ = c .netPoll .Remove (conn .conn )
718
723
conn .handler .ServerClose ()
719
724
}
720
- if c .epoll != nil {
721
- err := c .epoll .Close (false )
725
+ if c .netPoll != nil {
726
+ err := c .netPoll .Close (false )
722
727
if err != nil {
723
728
c .log .Error ("subscriptionClient.close" , abstractlogger .Error (err ))
724
729
}
725
730
}
726
731
}
727
732
728
733
func (c * subscriptionClient ) handleAddConn (conn * connection ) {
729
- if err := c .epoll .Add (conn .conn ); err != nil {
734
+ if err := c .netPoll .Add (conn .conn ); err != nil {
730
735
c .log .Error ("subscriptionClient.handleAddConn" , abstractlogger .Error (err ))
731
736
conn .handler .ServerClose ()
732
737
return
733
738
}
734
- c .epollState .connections [conn .fd ] = conn
735
- c .epollState .triggers [conn .id ] = conn .fd
739
+ c .netPollState .connections [conn .fd ] = conn
740
+ c .netPollState .triggers [conn .id ] = conn .fd
736
741
// when we previously had 0 connections, we will have 1 connection now
737
- // this means we need to start the ticker so that we get epoll events
738
- if len (c .epollState .connections ) == 1 {
739
- c .epollState .waitForEventsTicker = time .NewTicker (c .epollConfig .TickInterval )
740
- c .epollState .waitForEventsTick = c .epollState .waitForEventsTicker .C
741
- c .epollState .hasConnections .Store (true )
742
+ // this means we need to start the ticker so that we get netPoll events
743
+ if len (c .netPollState .connections ) == 1 {
744
+ c .netPollState .waitForEventsTicker = time .NewTicker (c .netPollConfig .TickInterval )
745
+ c .netPollState .waitForEventsTick = c .netPollState .waitForEventsTicker .C
746
+ c .netPollState .hasConnections .Store (true )
742
747
}
743
748
}
744
749
745
750
func (c * subscriptionClient ) handleClientUnsubscribe (id uint64 ) {
746
- fd , ok := c .epollState .triggers [id ]
751
+ fd , ok := c .netPollState .triggers [id ]
747
752
if ! ok {
748
753
return
749
754
}
750
- delete (c .epollState .triggers , id )
751
- conn , ok := c .epollState .connections [fd ]
755
+ delete (c .netPollState .triggers , id )
756
+ conn , ok := c .netPollState .connections [fd ]
752
757
if ! ok {
753
758
return
754
759
}
755
- delete (c .epollState .connections , fd )
756
- _ = c .epoll .Remove (conn .conn )
760
+ delete (c .netPollState .connections , fd )
761
+ _ = c .netPoll .Remove (conn .conn )
757
762
conn .handler .ClientClose ()
758
763
// if we have no connections left, we stop the ticker
759
- if len (c .epollState .connections ) == 0 {
760
- c .epollState .waitForEventsTicker .Stop ()
761
- c .epollState .waitForEventsTick = nil
762
- c .epollState .hasConnections .Store (false )
764
+ if len (c .netPollState .connections ) == 0 {
765
+ c .netPollState .waitForEventsTicker .Stop ()
766
+ c .netPollState .waitForEventsTick = nil
767
+ c .netPollState .hasConnections .Store (false )
763
768
}
764
769
}
765
770
766
771
func (c * subscriptionClient ) handleServerUnsubscribe (fd int ) {
767
- conn , ok := c .epollState .connections [fd ]
772
+ conn , ok := c .netPollState .connections [fd ]
768
773
if ! ok {
769
774
return
770
775
}
771
- delete (c .epollState .connections , fd )
772
- delete (c .epollState .triggers , conn .id )
773
- _ = c .epoll .Remove (conn .conn )
776
+ delete (c .netPollState .connections , fd )
777
+ delete (c .netPollState .triggers , conn .id )
778
+ _ = c .netPoll .Remove (conn .conn )
774
779
conn .handler .ServerClose ()
775
780
// if we have no connections left, we stop the ticker
776
- if len (c .epollState .connections ) == 0 {
777
- c .epollState .waitForEventsTicker .Stop ()
778
- c .epollState .waitForEventsTick = nil
779
- c .epollState .hasConnections .Store (false )
781
+ if len (c .netPollState .connections ) == 0 {
782
+ c .netPollState .waitForEventsTicker .Stop ()
783
+ c .netPollState .waitForEventsTick = nil
784
+ c .netPollState .hasConnections .Store (false )
780
785
}
781
786
}
782
787
0 commit comments