Skip to content

Commit b897b67

Browse files
author
Alex
authored
Register dispatcher in the default dig graph (#304)
Right now people are confused about rpc.Dispatcher(), which returns you a dispatcher only if service is started and testing handlers becomes much harder. Proposed solution is to inject it in the default dig graph.
1 parent 96faff9 commit b897b67

4 files changed

Lines changed: 82 additions & 25 deletions

File tree

modules/rpc/thrift.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ func newYARPCThriftModule(
6464
reg := func(mod *YARPCModule) {
6565
_setupMu.Lock()
6666
defer _setupMu.Unlock()
67-
Dispatcher().Register(registrants)
67+
68+
mod.controller.dispatcher.Register(registrants)
6869
}
6970

7071
return newYARPCModule(mi, reg, options...)

modules/rpc/thrift_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ import (
2525
"testing"
2626

2727
"go.uber.org/fx/config"
28+
"go.uber.org/fx/dig"
2829
"go.uber.org/fx/modules"
2930
"go.uber.org/fx/service"
3031

3132
"github.com/stretchr/testify/assert"
3233
"github.com/stretchr/testify/require"
34+
"go.uber.org/yarpc"
3335
"go.uber.org/yarpc/api/transport"
3436
"go.uber.org/yarpc/encoding/thrift"
3537
)
@@ -74,6 +76,11 @@ modules:
7476

7577
testInitRunModule(t, goofy[0], mci)
7678
testInitRunModule(t, gopher[0], mci)
79+
80+
// Dispatcher must be resolved in the default graph
81+
var dispatcher *yarpc.Dispatcher
82+
assert.NoError(t, dig.Resolve(&dispatcher))
83+
assert.Equal(t, 2, len(dispatcher.Inbounds()))
7784
}
7885

7986
func TestThriftModule_BadOptions(t *testing.T) {
@@ -105,7 +112,8 @@ func testInitRunModule(t *testing.T, mod service.Module, mci service.ModuleCreat
105112

106113
func mch() service.ModuleCreateInfo {
107114
return service.ModuleCreateInfo{
108-
Host: service.NopHost(),
115+
Host: service.NopHost(),
116+
Items: make(map[string]interface{}),
109117
}
110118
}
111119

modules/rpc/yarpc.go

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"context"
2525
"errors"
2626
"fmt"
27+
"strconv"
2728
"sync"
2829

2930
"go.uber.org/fx/modules"
@@ -32,6 +33,7 @@ import (
3233
"go.uber.org/fx/ulog"
3334

3435
errs "github.com/pkg/errors"
36+
"go.uber.org/fx/dig"
3537
"go.uber.org/yarpc"
3638
"go.uber.org/yarpc/api/middleware"
3739
"go.uber.org/yarpc/api/transport"
@@ -42,13 +44,17 @@ import (
4244
// YARPCModule is an implementation of a core RPC module using YARPC.
4345
// All the YARPC modules share the same dispatcher and middleware.
4446
// Dispatcher will start when any created module calls Start().
47+
// The YARPC team advised dispatcher to be a 'singleton' to control
48+
// the lifecycle of all of the in/out bound traffic, so we will
49+
// register it in a dig.Graph provided with options/default graph.
4550
type YARPCModule struct {
4651
modules.ModuleBase
47-
register registerServiceFunc
48-
config yarpcConfig
49-
log ulog.Log
50-
stateMu sync.RWMutex
51-
isRunning bool
52+
register registerServiceFunc
53+
config yarpcConfig
54+
log ulog.Log
55+
stateMu sync.RWMutex
56+
isRunning bool
57+
controller *dispatcherController
5258
}
5359

5460
var (
@@ -61,12 +67,6 @@ var (
6167
_starterFn = defaultYARPCStarter
6268

6369
_ service.Module = &YARPCModule{}
64-
65-
// Controller represents a collection of all YARPC configs
66-
// that are stored together to create a shared dispatcher.
67-
// The YARPC team advised it to be a 'singleton' to control
68-
// the lifecycle of all of the in/out bound traffic.
69-
_controller dispatcherController
7070
)
7171

7272
type registerServiceFunc func(module *YARPCModule)
@@ -91,6 +91,24 @@ type Inbound struct {
9191
HTTP *Address
9292
}
9393

94+
func (i *Inbound) String() string {
95+
if i == nil {
96+
return ""
97+
}
98+
99+
http := "none"
100+
if i.HTTP != nil {
101+
http = strconv.Itoa(i.HTTP.Port)
102+
}
103+
104+
tchannel := "none"
105+
if i.TChannel != nil {
106+
tchannel = strconv.Itoa(i.TChannel.Port)
107+
}
108+
109+
return fmt.Sprintf("Inbound:{HTTP: %s; TChannel: %s}", http, tchannel)
110+
}
111+
94112
// Address is a struct that have a required port for tchannel/http transports.
95113
// TODO(alsam) make it optional
96114
type Address struct {
@@ -111,7 +129,7 @@ type dispatcherController struct {
111129
startError error
112130

113131
configs []*yarpcConfig
114-
dispatcher *yarpc.Dispatcher
132+
dispatcher yarpc.Dispatcher
115133
}
116134

117135
// Adds the config to the controller
@@ -155,12 +173,14 @@ func (c *dispatcherController) Start(host service.Host) error {
155173

156174
_dispatcherMu.Lock()
157175
defer _dispatcherMu.Unlock()
158-
if c.dispatcher, err = _dispatcherFn(host, cfg); err != nil {
176+
var d *yarpc.Dispatcher
177+
if d, err = _dispatcherFn(host, cfg); err != nil {
159178
c.startError = err
160179
return
161180
}
162181

163-
c.startError = _starterFn(c.dispatcher)
182+
c.dispatcher = *d
183+
c.startError = _starterFn(&c.dispatcher)
164184
})
165185

166186
return c.startError
@@ -249,7 +269,24 @@ func newYARPCModule(
249269
module.config.inboundMiddleware = inboundMiddlewareFromCreateInfo(mi)
250270
module.config.onewayInboundMiddleware = onewayInboundMiddlewareFromCreateInfo(mi)
251271

252-
_controller.addConfig(module.config)
272+
// Try to resolve a controller first
273+
// TODO(alsam) use dig options when available, because we can overwrite the controller in case of multiple
274+
// modules registering a controller.
275+
if err := dig.Resolve(&module.controller); err != nil {
276+
277+
// Try to register it then
278+
module.controller = &dispatcherController{}
279+
if errCr := dig.Register(module.controller); errCr != nil {
280+
return nil, errs.Wrap(errCr, "can't register a dispatcher controller")
281+
}
282+
283+
// Register dispatcher
284+
if err := dig.Register(&module.controller.dispatcher); err != nil {
285+
return nil, errs.Wrap(err, "unable to register the dispatcher")
286+
}
287+
}
288+
289+
module.controller.addConfig(module.config)
253290

254291
module.log.Info("Module successfuly created", "inbounds", module.config.Inbounds)
255292

@@ -294,7 +331,7 @@ func (m *YARPCModule) Start(readyCh chan<- struct{}) <-chan error {
294331
defer m.stateMu.Unlock()
295332

296333
// TODO(alsam) allow services to advertise with a name separate from the host name.
297-
if err := _controller.Start(m.Host()); err != nil {
334+
if err := m.controller.Start(m.Host()); err != nil {
298335
ret <- errs.Wrap(err, "unable to start dispatcher")
299336
return ret
300337
}
@@ -316,8 +353,9 @@ func (m *YARPCModule) Stop() error {
316353

317354
m.stateMu.Lock()
318355
defer m.stateMu.Unlock()
356+
319357
m.isRunning = false
320-
return _controller.Stop()
358+
return m.controller.Stop()
321359
}
322360

323361
// IsRunning returns whether a module is running
@@ -354,9 +392,3 @@ func RegisterStarter(startFn StarterFn) {
354392
func defaultYARPCStarter(dispatcher *yarpc.Dispatcher) error {
355393
return dispatcher.Start()
356394
}
357-
358-
// Dispatcher returns a dispatcher that can be used to create clients.
359-
// It should be called after at least one module have been started, otherwise it will be nil.
360-
func Dispatcher() *yarpc.Dispatcher {
361-
return _controller.dispatcher
362-
}

modules/rpc/yarpc_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package rpc
2222

2323
import (
24+
"fmt"
2425
"testing"
2526

2627
"go.uber.org/fx/service"
@@ -68,3 +69,18 @@ func TestMergeOfEmptyConfigCollectionReturnsError(t *testing.T) {
6869
assert.EqualError(t, err, "unable to merge empty configs")
6970
assert.EqualError(t, c.Start(service.NopHost()), err.Error())
7071
}
72+
73+
func TestInboundPrint(t *testing.T) {
74+
t.Parallel()
75+
var i *Inbound
76+
assert.Equal(t, "", fmt.Sprint(i))
77+
78+
i = &Inbound{}
79+
assert.Equal(t, "Inbound:{HTTP: none; TChannel: none}", fmt.Sprint(i))
80+
i.HTTP = &Address{8080}
81+
assert.Equal(t, "Inbound:{HTTP: 8080; TChannel: none}", fmt.Sprint(i))
82+
i.TChannel = &Address{9876}
83+
assert.Equal(t, "Inbound:{HTTP: 8080; TChannel: 9876}", fmt.Sprint(i))
84+
i.HTTP = nil
85+
assert.Equal(t, "Inbound:{HTTP: none; TChannel: 9876}", fmt.Sprint(i))
86+
}

0 commit comments

Comments
 (0)