Skip to content

Commit 6df8fac

Browse files
authored
Verify registered listeners when starting the app. (ServiceWeaver#561)
This ensures that the application listeners match the information stored in the read-only section of the application binary, and used by various deployers. If there is a mismatch, the user will be prompted to run `weaver generate`. This fixes one of the common issues that came up in workshops, where people forgot to re-run `weaver generate` after declaring the listener.
1 parent fc2e881 commit 6df8fac

File tree

7 files changed

+99
-13
lines changed

7 files changed

+99
-13
lines changed

dev/build_and_test

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ function cmd_vet() {
5454

5555
function cmd_lint() {
5656
if ! exists staticcheck; then
57-
printf "staticcheck not found; install via\ngo install honnef.co/go/tools/cmd/staticcheck@0.4.3\n" >&2
57+
printf "staticcheck not found; install via\ngo install honnef.co/go/tools/cmd/staticcheck@v0.4.3\n" >&2
5858
exit 1
5959
fi
6060

fill.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func fillListeners(impl any, get func(name string) (net.Listener, string, error)
101101
// The listener's name is the field name, unless a tag is present.
102102
name := t.Name
103103
if tag, ok := t.Tag.Lookup("weaver"); ok {
104-
if !isValidListenerName(tag) {
104+
if !isValidListenerName(name) {
105105
return fmt.Errorf("FillListeners: listener tag %s is not a valid Go identifier", tag)
106106
}
107107
name = tag

godeps.txt

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ github.com/ServiceWeaver/weaver
1010
github.com/ServiceWeaver/weaver/runtime/codegen
1111
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
1212
go.opentelemetry.io/otel/trace
13+
golang.org/x/exp/slices
1314
log/slog
1415
math/rand
1516
net

internal/tool/generate/generator.go

+10
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,16 @@ func extractComponent(opt Options, pkg *packages.Package, file *ast.File, tset *
510510
return nil, err
511511
}
512512

513+
// Check that listener names are unique.
514+
seenLis := map[string]struct{}{}
515+
for _, lis := range listeners {
516+
if _, ok := seenLis[lis]; ok {
517+
return nil, errorf(pkg.Fset, spec.Pos(),
518+
"component implementation %s declares multiple listeners with name %s. Please disambiguate.", formatType(pkg, impl), lis)
519+
}
520+
seenLis[lis] = struct{}{}
521+
}
522+
513523
// Warn the user if the component has a mistyped Init method. Init methods
514524
// are supposed to have type "func(context.Context) error", but it's easy
515525
// to forget to add a context.Context argument or error return. Without
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// ERROR: multiple listeners with name bar
16+
17+
// Multiple listeners with the same name.
18+
package foo
19+
20+
import (
21+
"github.com/ServiceWeaver/weaver"
22+
)
23+
24+
type foo interface{}
25+
26+
type impl struct {
27+
weaver.Implements[foo]
28+
bar weaver.Listener
29+
_ weaver.Listener `weaver:"bar"`
30+
}

validate.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/ServiceWeaver/weaver/internal/reflection"
2424
"github.com/ServiceWeaver/weaver/runtime/codegen"
25+
"golang.org/x/exp/slices"
2526
)
2627

2728
// validateRegistrations validates the provided registrations, returning an
@@ -48,16 +49,25 @@ func validateRegistrations(regs []*codegen.Registration) error {
4849
if _, ok := intfs[v.Type]; !ok {
4950
// T is not a registered component interface.
5051
err := fmt.Errorf(
51-
"component implementation struct %v has field %v, but component %v was not registered; maybe you forgot to run 'weaver generate'",
52+
"component implementation struct %v has component reference field %v, but component %v was not registered; maybe you forgot to run 'weaver generate'",
5253
reg.Impl, f.Type, v.Type,
5354
)
5455
errs = append(errs, err)
5556
}
5657

5758
case f.Type == reflection.Type[Listener]():
5859
// f is a weaver.Listener.
59-
if tag, ok := f.Tag.Lookup("weaver"); ok && !isValidListenerName(tag) {
60-
err := fmt.Errorf("component implementation struct %v has invalid listener tag %q", reg.Impl, tag)
60+
name := f.Name
61+
if tag, ok := f.Tag.Lookup("weaver"); ok {
62+
if !isValidListenerName(tag) {
63+
err := fmt.Errorf("component implementation struct %v has invalid listener tag %q", reg.Impl, tag)
64+
errs = append(errs, err)
65+
continue
66+
}
67+
name = tag
68+
}
69+
if !slices.Contains(reg.Listeners, name) {
70+
err := fmt.Errorf("component implementation struct %v has a listener field %v, but listener %v hasn't been registered; maybe you forgot to run 'weaver generate'", reg.Impl, name, name)
6171
errs = append(errs, err)
6272
}
6373
}

validate_test.go

+43-8
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,21 @@ func TestValidateNoRegistrations(t *testing.T) {
3434
// TestValidateValidRegistrations tests that validateRegistrations succeeds on
3535
// a set of valid registrations.
3636
func TestValidateValidRegistrations(t *testing.T) {
37-
type foo struct{}
38-
type bar struct{}
37+
type foo interface{}
38+
type bar interface{}
3939
type fooImpl struct {
4040
Ref[bar]
41-
Listener `weaver:"foo"`
41+
Listener `weaver:"lis1"`
42+
_ Listener `weaver:"lis2"`
43+
lis3 Listener //lint:ignore U1000 Present for code generation.
4244
}
4345
type barImpl struct{ Ref[foo] }
4446
regs := []*codegen.Registration{
4547
{
46-
Name: "foo",
47-
Iface: reflection.Type[foo](),
48-
Impl: reflection.Type[fooImpl](),
48+
Name: "foo",
49+
Iface: reflection.Type[foo](),
50+
Impl: reflection.Type[fooImpl](),
51+
Listeners: []string{"lis1", "lis2", "lis3"},
4952
},
5053
{
5154
Name: "bar",
@@ -61,7 +64,7 @@ func TestValidateValidRegistrations(t *testing.T) {
6164
// TestValidateUnregisteredRef tests that validateRegistrations fails when a
6265
// component has a weaver.Ref on an unregistered component.
6366
func TestValidateUnregisteredRef(t *testing.T) {
64-
type foo struct{}
67+
type foo interface{}
6568
type fooImpl struct{ Ref[io.Reader] }
6669
regs := []*codegen.Registration{
6770
{
@@ -83,7 +86,7 @@ func TestValidateUnregisteredRef(t *testing.T) {
8386
// TestValidateInvalidListenerNames tests that validateRegistrations fails on
8487
// invalid listener names.
8588
func TestValidateInvalidListenerNames(t *testing.T) {
86-
type foo struct{}
89+
type foo interface{}
8790
type fooImpl struct {
8891
_ Listener `weaver:""` // empty name
8992
_ Listener `weaver:" "` // whitespace name
@@ -114,3 +117,35 @@ func TestValidateInvalidListenerNames(t *testing.T) {
114117
}
115118
}
116119
}
120+
121+
// TestValidateUnregisteredListeners tests that validateRegistrations fails on
122+
// listener names that haven't been registered.
123+
func TestValidateUnregisteredListener(t *testing.T) {
124+
type foo interface{}
125+
type fooImpl struct {
126+
foo Listener //lint:ignore U1000 Present for code generation.
127+
bar Listener //lint:ignore U1000 Present for code generation.
128+
baz Listener //lint:ignore U1000 Present for code generation.
129+
}
130+
131+
regs := []*codegen.Registration{
132+
{
133+
Name: "foo",
134+
Iface: reflection.Type[foo](),
135+
Impl: reflection.Type[fooImpl](),
136+
Listeners: []string{"foo"},
137+
},
138+
}
139+
err := validateRegistrations(regs)
140+
if err == nil {
141+
t.Fatal("unexpected validateRegistrations success")
142+
}
143+
for _, want := range []string{
144+
`listener bar hasn't been registered`,
145+
`listener baz hasn't been registered`,
146+
} {
147+
if !strings.Contains(err.Error(), want) {
148+
t.Errorf("validateRegistrations: got %q, want %q", err, want)
149+
}
150+
}
151+
}

0 commit comments

Comments
 (0)