Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions