Skip to content

kitex注册使用ServiceMiddleware,会导致该midlleware最终执行多次 #1956

@kom0055

Description

@kom0055

Describe the bug

ServiceMiddleware因重新赋值,导致next mw紊乱

To Reproduce

如下,背景是使用kitex multiservice的模式,在注册s2时,我想使用ServiceMiddleware,打印"ServiceMiddleware Run"。预期是s2的接口会进入此mw并打印一次,s1的接口不会进入。 但事实发现只要s2的接口被调用一次后,s1和s2的接口都能进入此mw, 并且随着s2的接口调用次数变多,此mw执行次数线性增长。举个例子s2接口调用10次后,再次调用s2接口,会进入此mw 11次,打印11次

func main() {
	defer logs.Stop()
	var (
		s1      ServiceA     = new(_s1)
		s2 ServiceB = new(_s2)
	)
	svc := motorpriceservice.NewServer(s1, server.WithMiddleware(func(next endpoint.Endpoint) endpoint.Endpoint {

		return func(ctx context.Context, req, resp interface{}) (err error) {
			fmt.Print("loglog")

			err = next(ctx, req, resp)
			return
		}
	}))
	if err := tradepricecoreservice.RegisterService(svc, s2, cwgsvr.WithServiceMiddleware(
		func(next endpoint.Endpoint) endpoint.Endpoint {
			return func(ctx context.Context, req, resp interface{}) (err error) {
				fmt.Print("ServiceMiddleware Run")

				return next(ctx, req, resp)
			}
		},
	)); err != nil {
		panic(err)
	}
	if err := svc.Run(); err != nil {
		panic(err)
	}
}

推测问题在于此处重新赋值next,导致闭包变量产生了变哈。

本来调用栈 next1->next2->next3->next4,如果在执行next3的时候换了闭包变量next, 变成next1->next2->next3->next5->next4->invoke;如果再次调用变成 成next1->next2->next3->next5->next5->next4->invoke, 随着调用次数变多,此链路会线性增长,执行非预期的次数,更会导致运行时调用栈溢出。。更不用说并发情况下的更复杂的问题。

Image

用了单测复现根因

func TestChain(t *testing.T) {
	stuckMW := func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, req, resp interface{}) (err error) {
			t.Log("layer stuck")

			return next(ctx, req, resp)
		}
	}
	c := endpoint.Chain(func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, req, resp interface{}) (err error) {
			t.Log("layer1")
			return next(ctx, req, resp)
		}
	},
		func(next endpoint.Endpoint) endpoint.Endpoint {
			return func(ctx context.Context, req, resp interface{}) (err error) {
				t.Log("layer2")
				next = stuckMW(next)
				return next(ctx, req, resp)
			}
		},
		func(next endpoint.Endpoint) endpoint.Endpoint {
			return func(ctx context.Context, req, resp interface{}) (err error) {
				t.Log("layer3")
				return next(ctx, req, resp)
			}
		},
	)
	invokeChain := c(func(ctx context.Context, req, resp interface{}) (err error) {
		t.Log("invoke")
		return
	})
	for i := 0; i < 4; i++ {
		_ = invokeChain(context.Background(), nil, nil)
	}
}

输出结果

=== RUN   TestChain
    cmd_test.go:27: layer1
    cmd_test.go:33: layer2
    cmd_test.go:20: layer stuck
    cmd_test.go:40: layer3
    cmd_test.go:46: invoke
    cmd_test.go:27: layer1
    cmd_test.go:33: layer2
    cmd_test.go:20: layer stuck
    cmd_test.go:20: layer stuck
    cmd_test.go:40: layer3
    cmd_test.go:46: invoke
    cmd_test.go:27: layer1
    cmd_test.go:33: layer2
    cmd_test.go:20: layer stuck
    cmd_test.go:20: layer stuck
    cmd_test.go:20: layer stuck
    cmd_test.go:40: layer3
    cmd_test.go:46: invoke
    cmd_test.go:27: layer1
    cmd_test.go:33: layer2
    cmd_test.go:20: layer stuck
    cmd_test.go:20: layer stuck
    cmd_test.go:20: layer stuck
    cmd_test.go:20: layer stuck
    cmd_test.go:40: layer3
    cmd_test.go:46: invoke

Environment:

  • Kitex version: 0.14.1
  • Protocol: thrift

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions