Skip to content

Commit b29b40e

Browse files
committed
grpc: Split call of net.Listen out from Serve
This is going to be useful for the next commit that fixes up some flakiness in this package's tests.
1 parent 435f92b commit b29b40e

2 files changed

Lines changed: 110 additions & 19 deletions

File tree

grpc/grpc.go

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net"
1010
"strconv"
11+
"sync"
1112

1213
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
1314
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
@@ -22,13 +23,17 @@ import (
2223
// Server is used to hold configured info and ultimately the grpc server
2324
type Server struct {
2425
err error
25-
port int
2626
server *grpc.Server
2727
cert tls.Certificate
2828
options []grpc.ServerOption
2929
streamers []grpc.StreamServerInterceptor
3030
unariers []grpc.UnaryServerInterceptor
3131
registry []func(*grpc.Server)
32+
33+
mu sync.RWMutex
34+
port int
35+
once sync.Once
36+
listener net.Listener
3237
}
3338

3439
// The ServiceRegister type is used as a callback once the underlying grpc server is setup to register the main service.
@@ -99,6 +104,8 @@ func NewServer(l log.Logger, reg ServiceRegister, options ...Option) (*Server, e
99104

100105
// Port returns the port the server is listening on
101106
func (s *Server) Port() int {
107+
s.mu.RLock()
108+
defer s.mu.RUnlock()
102109
return s.port
103110
}
104111

@@ -107,15 +114,49 @@ func (s *Server) Server() *grpc.Server {
107114
return s.server
108115
}
109116

117+
func (s *Server) listen() error {
118+
var err error
119+
s.once.Do(func() {
120+
s.mu.RLock()
121+
port := s.port
122+
s.mu.RUnlock()
123+
124+
s.listener, err = net.Listen("tcp", fmt.Sprintf(":%d", port)) //nolint:ineffassign // we do in fact want to assign to err in the outer scope
125+
if err != nil {
126+
err = errors.Wrap(err, "listen")
127+
return
128+
}
129+
130+
var p string
131+
_, p, err = net.SplitHostPort(s.listener.Addr().String()) //nolint:ineffassign // we do in fact want to assign to err in the outer scope
132+
if err != nil {
133+
err = errors.Wrap(err, "extract server port")
134+
return
135+
}
136+
137+
port, err = strconv.Atoi(p)
138+
if err != nil {
139+
err = errors.Wrap(err, "parse server port")
140+
return
141+
}
142+
143+
s.mu.Lock()
144+
s.port = port
145+
s.mu.Unlock()
146+
})
147+
148+
return err
149+
}
150+
110151
// Serve starts the grpc server
111152
func (s *Server) Serve() error {
112-
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.port))
153+
err := s.listen()
113154
if err != nil {
114-
return errors.Wrap(err, "listen")
155+
return err
115156
}
116-
defer lis.Close()
117157

118-
return errors.Wrap(s.server.Serve(lis), "serve")
158+
defer s.listener.Close()
159+
return errors.Wrap(s.server.Serve(s.listener), "serve")
119160
}
120161

121162
// ServerOption will add the opt param to the underlying grpc.NewServer() call.

grpc/grpc_test.go

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/packethost/pkg/internal/testenv"
2424
"github.com/packethost/pkg/log"
2525
"github.com/stretchr/testify/require"
26+
assert "github.com/stretchr/testify/require"
2627
"google.golang.org/grpc"
2728
"google.golang.org/grpc/credentials"
2829
pb "google.golang.org/grpc/examples/helloworld/helloworld"
@@ -65,7 +66,7 @@ func TestDefaultsAndOrdering(t *testing.T) {
6566
s, err := NewServer(l, srv, pre1, Register(reg1), pre2, Register(reg2))
6667
assert.NoError(err)
6768
assert.NotNil(s)
68-
assert.Equal(s.port, 8080)
69+
assert.Equal(s.Port(), 8080)
6970

7071
// ensure order
7172
assert.True(pre1I > 0)
@@ -84,7 +85,7 @@ func TestPort(t *testing.T) {
8485
s, err := NewServer(l, defSrv)
8586
assert.NoError(err)
8687
assert.NotNil(s)
87-
assert.Equal(s.port, 8080)
88+
assert.Equal(s.Port(), 8080)
8889
assert.Equal(s.Port(), 8080)
8990

9091
os.Setenv("GRPC_PORT", "4242")
@@ -93,13 +94,13 @@ func TestPort(t *testing.T) {
9394
s, err = NewServer(l, defSrv)
9495
assert.NoError(err)
9596
assert.NotNil(s)
96-
assert.Equal(s.port, 4242)
97+
assert.Equal(s.Port(), 4242)
9798
assert.Equal(s.Port(), 4242)
9899

99100
s, err = NewServer(l, defSrv, Port(2424))
100101
assert.NoError(err)
101102
assert.NotNil(s)
102-
assert.Equal(s.port, 2424)
103+
assert.Equal(s.Port(), 2424)
103104
assert.Equal(s.Port(), 2424)
104105

105106
os.Setenv("GRPC_PORT", "0")
@@ -245,7 +246,7 @@ func TestX509(t *testing.T) {
245246
assert.NoError(err)
246247
assert.NotNil(s)
247248
serve(t, s, func() {
248-
assert.NoError(connectGRPC(t, s.port, ""))
249+
assert.NoError(connectGRPC(t, s.Port(), ""))
249250
})
250251
})
251252

@@ -262,8 +263,8 @@ func TestX509(t *testing.T) {
262263
assert.NoError(err)
263264
assert.NotNil(s)
264265
serve(t, s, func() {
265-
assert.Error(connectGRPC(t, s.port, ""))
266-
assert.NoError(connectGRPC(t, s.port, certE))
266+
assert.Error(connectGRPC(t, s.Port(), ""))
267+
assert.NoError(connectGRPC(t, s.Port(), certE))
267268
})
268269
})
269270

@@ -276,9 +277,9 @@ func TestX509(t *testing.T) {
276277
assert.NoError(err)
277278
assert.NotNil(s)
278279
serve(t, s, func() {
279-
assert.Error(connectGRPC(t, s.port, ""))
280-
assert.Error(connectGRPC(t, s.port, certE))
281-
assert.NoError(connectGRPC(t, s.port, certKP))
280+
assert.Error(connectGRPC(t, s.Port(), ""))
281+
assert.Error(connectGRPC(t, s.Port(), certE))
282+
assert.NoError(connectGRPC(t, s.Port(), certKP))
282283
})
283284
})
284285

@@ -292,10 +293,10 @@ func TestX509(t *testing.T) {
292293
assert.NoError(err)
293294
assert.NotNil(s)
294295
serve(t, s, func() {
295-
assert.Error(connectGRPC(t, s.port, ""))
296-
assert.Error(connectGRPC(t, s.port, certE))
297-
assert.Error(connectGRPC(t, s.port, certKP))
298-
assert.NoError(connectGRPC(t, s.port, certLKP))
296+
assert.Error(connectGRPC(t, s.Port(), ""))
297+
assert.Error(connectGRPC(t, s.Port(), certE))
298+
assert.Error(connectGRPC(t, s.Port(), certKP))
299+
assert.NoError(connectGRPC(t, s.Port(), certLKP))
299300
})
300301
})
301302

@@ -308,3 +309,52 @@ func TestX509(t *testing.T) {
308309
assert.Nil(s)
309310
})
310311
}
312+
313+
func TestListen(t *testing.T) {
314+
t.Run("invalid port fails", func(t *testing.T) {
315+
s := Server{port: -1}
316+
assert.Error(t, s.listen())
317+
assert.Nil(t, s.listener)
318+
})
319+
t.Run("port 0 updates .port", func(t *testing.T) {
320+
s := Server{}
321+
assert.NoError(t, s.listen())
322+
assert.NotNil(t, s.listener)
323+
defer s.listener.Close()
324+
325+
assert.NotZero(t, s.Port())
326+
})
327+
t.Run("can run 2 servers by using port 0", func(t *testing.T) {
328+
s1 := Server{}
329+
assert.NoError(t, s1.listen())
330+
assert.NotNil(t, s1.listener)
331+
defer s1.listener.Close()
332+
assert.NotZero(t, s1.port)
333+
334+
s2 := Server{}
335+
assert.NoError(t, s2.listen())
336+
assert.NotNil(t, s2.listener)
337+
defer s2.listener.Close()
338+
assert.NotZero(t, s2.port)
339+
340+
assert.NotEqual(t, s1.port, s2.port)
341+
})
342+
t.Run("Serve calls s.listen if necessary", func(t *testing.T) {
343+
l := log.Test(t, t.Name())
344+
345+
s, err := NewServer(l, defSrv)
346+
assert.NoError(t, err)
347+
assert.NotNil(t, s)
348+
349+
go func() {
350+
if err := s.Serve(); err != nil {
351+
panic(err)
352+
}
353+
}()
354+
355+
for port := s.Port(); port == 0; port = s.Port() {
356+
time.Sleep(100 * time.Microsecond)
357+
}
358+
assert.NoError(t, connectGRPC(t, s.Port(), ""))
359+
})
360+
}

0 commit comments

Comments
 (0)