Skip to content

Commit f21d0c7

Browse files
removing ResultIterator in favor of a result channel
1 parent d6ffa49 commit f21d0c7

File tree

3 files changed

+56
-188
lines changed

3 files changed

+56
-188
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
module github.com/graphql-go/graphql
2+
3+
go 1.13

subscription.go

Lines changed: 13 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -3,148 +3,11 @@ package graphql
33
import (
44
"context"
55
"fmt"
6-
"sync"
76

87
"github.com/graphql-go/graphql/gqlerrors"
98
"github.com/graphql-go/graphql/language/ast"
109
)
1110

12-
// Subscriber subscriber
13-
type Subscriber struct {
14-
message chan interface{}
15-
done chan interface{}
16-
}
17-
18-
// Message returns the subscriber message channel
19-
func (c *Subscriber) Message() chan interface{} {
20-
return c.message
21-
}
22-
23-
// Done returns the subscriber done channel
24-
func (c *Subscriber) Done() chan interface{} {
25-
return c.done
26-
}
27-
28-
// NewSubscriber creates a new subscriber
29-
func NewSubscriber(message, done chan interface{}) *Subscriber {
30-
return &Subscriber{
31-
message: message,
32-
done: done,
33-
}
34-
}
35-
36-
// ResultIteratorParams parameters passed to the result iterator handler
37-
type ResultIteratorParams struct {
38-
ResultCount int64 // number of results this iterator has processed
39-
Result *Result // the current result
40-
Done func() // Removes the current handler
41-
Cancel func() // Cancels the iterator, same as iterator.Cancel()
42-
}
43-
44-
// ResultIteratorFn a result iterator handler
45-
type ResultIteratorFn func(p ResultIteratorParams)
46-
47-
// holds subscription handler data
48-
type subscriptionHanlderConfig struct {
49-
handler ResultIteratorFn
50-
doneFunc func()
51-
}
52-
53-
// ResultIterator handles processing results from a chan *Result
54-
type ResultIterator struct {
55-
currentHandlerID int64
56-
count int64
57-
mx sync.Mutex
58-
ch chan *Result
59-
iterDone chan interface{}
60-
subDone chan interface{}
61-
cancelled bool
62-
handlers map[int64]*subscriptionHanlderConfig
63-
}
64-
65-
func (c *ResultIterator) incrimentCount() int64 {
66-
c.mx.Lock()
67-
defer c.mx.Unlock()
68-
c.count++
69-
return c.count
70-
}
71-
72-
// NewResultIterator creates a new iterator and starts handling message on the result channel
73-
func NewResultIterator(subDone chan interface{}, ch chan *Result) *ResultIterator {
74-
iterator := &ResultIterator{
75-
currentHandlerID: 0,
76-
count: 0,
77-
iterDone: make(chan interface{}),
78-
subDone: subDone,
79-
ch: ch,
80-
cancelled: false,
81-
handlers: map[int64]*subscriptionHanlderConfig{},
82-
}
83-
84-
go func() {
85-
for {
86-
select {
87-
case <-iterator.iterDone:
88-
subDone <- true
89-
return
90-
case res := <-iterator.ch:
91-
if iterator.cancelled {
92-
return
93-
}
94-
95-
count := iterator.incrimentCount()
96-
for _, h := range iterator.handlers {
97-
h.handler(ResultIteratorParams{
98-
ResultCount: int64(count),
99-
Result: res,
100-
Done: h.doneFunc,
101-
Cancel: iterator.Cancel,
102-
})
103-
}
104-
}
105-
}
106-
}()
107-
108-
return iterator
109-
}
110-
111-
// adds a new handler
112-
func (c *ResultIterator) addHandler(handler ResultIteratorFn) {
113-
c.mx.Lock()
114-
defer c.mx.Unlock()
115-
116-
handlerID := c.currentHandlerID + 1
117-
c.currentHandlerID = handlerID
118-
c.handlers[handlerID] = &subscriptionHanlderConfig{
119-
handler: handler,
120-
doneFunc: func() {
121-
c.removeHandler(handlerID)
122-
},
123-
}
124-
}
125-
126-
// removes a handler and cancels if no more handlers exist
127-
func (c *ResultIterator) removeHandler(handlerID int64) {
128-
c.mx.Lock()
129-
defer c.mx.Unlock()
130-
131-
delete(c.handlers, handlerID)
132-
if len(c.handlers) == 0 {
133-
c.Cancel()
134-
}
135-
}
136-
137-
// ForEach adds a handler and handles each message as they come
138-
func (c *ResultIterator) ForEach(handler ResultIteratorFn) {
139-
c.addHandler(handler)
140-
}
141-
142-
// Cancel cancels the iterator
143-
func (c *ResultIterator) Cancel() {
144-
c.cancelled = true
145-
c.iterDone <- true
146-
}
147-
14811
// SubscribeParams parameters for subscribing
14912
type SubscribeParams struct {
15013
Schema Schema
@@ -158,14 +21,8 @@ type SubscribeParams struct {
15821
}
15922

16023
// Subscribe performs a subscribe operation
161-
func Subscribe(p SubscribeParams) *ResultIterator {
24+
func Subscribe(ctx context.Context, p SubscribeParams) chan *Result {
16225
resultChannel := make(chan *Result)
163-
doneChannel := make(chan interface{})
164-
// Use background context if no context was provided
165-
ctx := p.ContextValue
166-
if ctx == nil {
167-
ctx = context.Background()
168-
}
16926

17027
var mapSourceToResponse = func(payload interface{}) *Result {
17128
return Execute(ExecuteParams{
@@ -174,18 +31,18 @@ func Subscribe(p SubscribeParams) *ResultIterator {
17431
AST: p.Document,
17532
OperationName: p.OperationName,
17633
Args: p.VariableValues,
177-
Context: ctx,
34+
Context: p.ContextValue,
17835
})
17936
}
18037

18138
go func() {
18239
result := &Result{}
18340
defer func() {
18441
if err := recover(); err != nil {
185-
fmt.Println("SUBSCRIPTION RECOVERER", err)
18642
result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error)))
43+
resultChannel <- result
18744
}
188-
resultChannel <- result
45+
close(resultChannel)
18946
}()
19047

19148
exeContext, err := buildExecutionContext(buildExecutionCtxParams{
@@ -195,7 +52,7 @@ func Subscribe(p SubscribeParams) *ResultIterator {
19552
OperationName: p.OperationName,
19653
Args: p.VariableValues,
19754
Result: result,
198-
Context: ctx,
55+
Context: p.ContextValue,
19956
})
20057

20158
if err != nil {
@@ -263,7 +120,7 @@ func Subscribe(p SubscribeParams) *ResultIterator {
263120
Source: p.RootValue,
264121
Args: args,
265122
Info: info,
266-
Context: ctx,
123+
Context: p.ContextValue,
267124
})
268125
if err != nil {
269126
result.Errors = append(result.Errors, gqlerrors.FormatError(err.(error)))
@@ -279,14 +136,14 @@ func Subscribe(p SubscribeParams) *ResultIterator {
279136
}
280137

281138
switch fieldResult.(type) {
282-
case *Subscriber:
283-
sub := fieldResult.(*Subscriber)
139+
case chan interface{}:
140+
sub := fieldResult.(chan interface{})
284141
for {
285142
select {
286-
case <-doneChannel:
287-
sub.done <- true
143+
case <-ctx.Done():
288144
return
289-
case res := <-sub.message:
145+
146+
case res := <-sub:
290147
resultChannel <- mapSourceToResponse(res)
291148
}
292149
}
@@ -296,6 +153,6 @@ func Subscribe(p SubscribeParams) *ResultIterator {
296153
}
297154
}()
298155

299-
// return a result iterator
300-
return NewResultIterator(doneChannel, resultChannel)
156+
// return a result channel
157+
return resultChannel
301158
}

subscription_test.go

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ func TestSubscription(t *testing.T) {
5252
return fmt.Sprintf("count=%v", p.Source), nil
5353
},
5454
Subscribe: func(p ResolveParams) (interface{}, error) {
55-
sub := NewSubscriber(m, make(chan interface{}))
56-
return sub, nil
55+
return m, nil
5756
},
5857
},
5958
"watch_should_fail": &Field{
@@ -74,60 +73,70 @@ func TestSubscription(t *testing.T) {
7473
return
7574
}
7675

77-
failIterator := Subscribe(SubscribeParams{
76+
// test a subscribe that should fail due to no return value
77+
fctx, fCancelFunc := context.WithCancel(context.Background())
78+
fail := Subscribe(fctx, SubscribeParams{
7879
Schema: schema,
7980
Document: document2,
8081
})
8182

82-
// test a subscribe that should fail due to no return value
83-
failIterator.ForEach(func(p ResultIteratorParams) {
84-
if !p.Result.HasErrors() {
85-
t.Errorf("subscribe failed to catch nil result from subscribe")
86-
p.Done()
83+
go func() {
84+
for {
85+
result := <-fail
86+
if !result.HasErrors() {
87+
t.Errorf("subscribe failed to catch nil result from subscribe")
88+
}
89+
fCancelFunc()
8790
return
8891
}
89-
p.Done()
90-
return
91-
})
92+
}()
9293

93-
resultIterator := Subscribe(SubscribeParams{
94+
// test subscription data
95+
resultCount := 0
96+
rctx, rCancelFunc := context.WithCancel(context.Background())
97+
results := Subscribe(rctx, SubscribeParams{
9498
Schema: schema,
9599
Document: document1,
96100
ContextValue: context.Background(),
97101
})
98102

99-
resultIterator.ForEach(func(p ResultIteratorParams) {
100-
if p.Result.HasErrors() {
101-
t.Errorf("subscribe error(s): %v", p.Result.Errors)
102-
p.Done()
103-
return
104-
}
105-
106-
if p.Result.Data != nil {
107-
data := p.Result.Data.(map[string]interface{})["watch_count"]
108-
expected := fmt.Sprintf("count=%d", p.ResultCount)
109-
actual := fmt.Sprintf("%v", data)
110-
if actual != expected {
111-
t.Errorf("subscription result error: expected %q, actual %q", expected, actual)
112-
p.Done()
103+
go func() {
104+
for {
105+
result := <-results
106+
if result.HasErrors() {
107+
t.Errorf("subscribe error(s): %v", result.Errors)
108+
rCancelFunc()
113109
return
114110
}
115111

116-
// test the done func by quitting after 3 iterations
117-
// the publisher will publish up to 5
118-
if p.ResultCount >= int64(maxPublish-2) {
119-
p.Done()
120-
return
112+
if result.Data != nil {
113+
resultCount++
114+
data := result.Data.(map[string]interface{})["watch_count"]
115+
expected := fmt.Sprintf("count=%d", resultCount)
116+
actual := fmt.Sprintf("%v", data)
117+
if actual != expected {
118+
t.Errorf("subscription result error: expected %q, actual %q", expected, actual)
119+
rCancelFunc()
120+
return
121+
}
122+
123+
// test the done func by quitting after 3 iterations
124+
// the publisher will publish up to 5
125+
if resultCount >= maxPublish-2 {
126+
rCancelFunc()
127+
return
128+
}
121129
}
122130
}
123-
})
131+
}()
124132

125133
// start publishing
126134
go func() {
127135
for i := 1; i <= maxPublish; i++ {
128136
time.Sleep(200 * time.Millisecond)
129137
m <- i
130138
}
139+
close(m)
131140
}()
132141

133142
// give time for the test to complete

0 commit comments

Comments
 (0)