github.com/cloudwego/kitex@v0.9.0/client/client_test.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package client 18 19 import ( 20 "context" 21 "errors" 22 "net" 23 "runtime" 24 "runtime/debug" 25 "strings" 26 "sync/atomic" 27 "testing" 28 "time" 29 30 "github.com/bytedance/gopkg/cloud/metainfo" 31 "github.com/cloudwego/localsession" 32 "github.com/cloudwego/localsession/backup" 33 "github.com/cloudwego/netpoll" 34 "github.com/golang/mock/gomock" 35 36 "github.com/cloudwego/kitex/client/callopt" 37 "github.com/cloudwego/kitex/internal/client" 38 "github.com/cloudwego/kitex/internal/mocks" 39 mocksnetpoll "github.com/cloudwego/kitex/internal/mocks/netpoll" 40 mocksremote "github.com/cloudwego/kitex/internal/mocks/remote" 41 "github.com/cloudwego/kitex/internal/mocks/rpc_info" 42 mocksstats "github.com/cloudwego/kitex/internal/mocks/stats" 43 mockthrift "github.com/cloudwego/kitex/internal/mocks/thrift" 44 internal_stream "github.com/cloudwego/kitex/internal/stream" 45 "github.com/cloudwego/kitex/internal/test" 46 "github.com/cloudwego/kitex/pkg/endpoint" 47 "github.com/cloudwego/kitex/pkg/fallback" 48 "github.com/cloudwego/kitex/pkg/kerrors" 49 "github.com/cloudwego/kitex/pkg/remote" 50 "github.com/cloudwego/kitex/pkg/retry" 51 "github.com/cloudwego/kitex/pkg/rpcinfo" 52 "github.com/cloudwego/kitex/pkg/rpcinfo/remoteinfo" 53 "github.com/cloudwego/kitex/pkg/rpctimeout" 54 "github.com/cloudwego/kitex/pkg/serviceinfo" 55 "github.com/cloudwego/kitex/pkg/stats" 56 "github.com/cloudwego/kitex/pkg/utils" 57 "github.com/cloudwego/kitex/pkg/warmup" 58 "github.com/cloudwego/kitex/transport" 59 ) 60 61 var ( 62 // method="mock" typeID=thrift.REPLY seqID=1 63 bs = []byte{128, 1, 0, 2, 0, 0, 0, 4, 109, 111, 99, 107, 0, 0, 0, 1} 64 mockWarmupOption = &warmup.ClientOption{ 65 ErrorHandling: warmup.ErrorLog, 66 ResolverOption: &warmup.ResolverOption{ 67 Dests: []*rpcinfo.EndpointBasicInfo{ 68 { 69 ServiceName: "mock_service", 70 Method: "mock_method", 71 }, 72 }, 73 }, 74 PoolOption: &warmup.PoolOption{ 75 ConnNum: 128, 76 Parallel: 128, 77 }, 78 } 79 ) 80 81 func newMockConn(ctrl *gomock.Controller) netpoll.Connection { 82 conn := mocksnetpoll.NewMockConnection(ctrl) 83 conn.EXPECT().Read(gomock.Any()).DoAndReturn(func(b []byte) (int, error) { 84 return copy(b, bs), nil 85 }).AnyTimes() 86 conn.EXPECT().Write(gomock.Any()).DoAndReturn(func(b []byte) (n int, err error) { 87 return len(b), nil 88 }).AnyTimes() 89 conn.EXPECT().RemoteAddr().Return(utils.NewNetAddr("tcp", "mock")).AnyTimes() 90 conn.EXPECT().IsActive().Return(true).AnyTimes() 91 conn.EXPECT().Close().Return(nil).AnyTimes() 92 return conn 93 } 94 95 func newDialer(ctrl *gomock.Controller) remote.Dialer { 96 conn := newMockConn(ctrl) 97 dialer := mocksremote.NewMockDialer(ctrl) 98 dialer.EXPECT().DialTimeout(gomock.Any(), gomock.Any(), gomock.Any()).Return(conn, nil).AnyTimes() 99 return dialer 100 } 101 102 func newMockCliTransHandlerFactory(ctrl *gomock.Controller) remote.ClientTransHandlerFactory { 103 handler := mocksremote.NewMockClientTransHandler(ctrl) 104 handler.EXPECT().OnMessage(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, args, result remote.Message) (context.Context, error) { 105 return ctx, nil 106 }).AnyTimes() 107 handler.EXPECT().Read(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, conn net.Conn, msg remote.Message) (context.Context, error) { 108 return ctx, nil 109 }).AnyTimes() 110 handler.EXPECT().Write(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, conn net.Conn, msg remote.Message) (context.Context, error) { 111 return ctx, nil 112 }).AnyTimes() 113 handler.EXPECT().SetPipeline(gomock.Any()).AnyTimes() 114 handler.EXPECT().OnError(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() 115 handler.EXPECT().OnInactive(gomock.Any(), gomock.Any()).AnyTimes() 116 factory := mocksremote.NewMockClientTransHandlerFactory(ctrl) 117 factory.EXPECT().NewTransHandler(gomock.Any()).DoAndReturn(func(opt *remote.ClientOption) (remote.ClientTransHandler, error) { 118 return handler, nil 119 }).AnyTimes() 120 return factory 121 } 122 123 func newMockClient(tb testing.TB, ctrl *gomock.Controller, extra ...Option) Client { 124 opts := []Option{ 125 WithTransHandlerFactory(newMockCliTransHandlerFactory(ctrl)), 126 WithResolver(resolver404(ctrl)), 127 WithDialer(newDialer(ctrl)), 128 WithDestService("destService"), 129 } 130 opts = append(opts, extra...) 131 132 svcInfo := mocks.ServiceInfo() 133 cli, err := NewClient(svcInfo, opts...) 134 test.Assert(tb, err == nil) 135 136 return cli 137 } 138 139 func TestCall(t *testing.T) { 140 ctrl := gomock.NewController(t) 141 defer ctrl.Finish() 142 143 mtd := mocks.MockMethod 144 cli := newMockClient(t, ctrl) 145 ctx := context.Background() 146 req := new(MockTStruct) 147 res := new(MockTStruct) 148 149 err := cli.Call(ctx, mtd, req, res) 150 test.Assert(t, err == nil, err) 151 } 152 153 func TestCallWithContextBackup(t *testing.T) { 154 localsession.InitDefaultManager(localsession.DefaultManagerOptions()) 155 d, dd := "d", "dd" 156 ctx := context.WithValue(context.Background(), "a", "a") 157 ctx = context.WithValue(ctx, d, dd) 158 ctx = metainfo.WithPersistentValues(ctx, "b", "b", "c", "c") 159 ctx = metainfo.WithValues(ctx, "e", "e") 160 backup.BackupCtx(ctx) 161 162 ctrl := gomock.NewController(t) 163 defer ctrl.Finish() 164 165 mtd := mocks.MockMethod 166 cli := newMockClient(t, ctrl, WithContextBackup(func(prev, cur context.Context) (ctx context.Context, backup bool) { 167 if v := prev.Value(d); v != nil { 168 cur = context.WithValue(cur, d, v) 169 } 170 return cur, true 171 }), WithMiddleware(func(e endpoint.Endpoint) endpoint.Endpoint { 172 return func(ctx context.Context, req, res interface{}) error { 173 test.Assert(t, ctx.Value("a") == "aa") 174 b, _ := metainfo.GetPersistentValue(ctx, "b") 175 test.Assert(t, b == "bb") 176 c, _ := metainfo.GetPersistentValue(ctx, "c") 177 test.Assert(t, c == "c") 178 test.Assert(t, ctx.Value(d) == dd) 179 _, ok := metainfo.GetPersistentValue(ctx, "e") 180 test.Assert(t, !ok) 181 f, _ := metainfo.GetValue(ctx, "f") 182 test.Assert(t, f == "f") 183 return nil 184 } 185 })) 186 req := new(MockTStruct) 187 res := new(MockTStruct) 188 189 ctx2 := context.WithValue(context.Background(), "a", "aa") 190 ctx2 = metainfo.WithPersistentValue(ctx2, "b", "bb") 191 ctx2 = metainfo.WithValue(ctx2, "f", "f") 192 err := cli.Call(ctx2, mtd, req, res) 193 test.Assert(t, err == nil, err) 194 } 195 196 func TestWithRetryOption(t *testing.T) { 197 ctrl := gomock.NewController(t) 198 defer ctrl.Finish() 199 200 mockRetryContainer := retry.NewRetryContainer() 201 cli := newMockClient(t, ctrl, WithRetryContainer(mockRetryContainer)) 202 203 test.Assert(t, cli.(*kcFinalizerClient).opt.RetryContainer == mockRetryContainer) 204 } 205 206 func BenchmarkCall(b *testing.B) { 207 ctrl := gomock.NewController(b) 208 defer ctrl.Finish() 209 210 mtd := mocks.MockExceptionMethod 211 cli := newMockClient(b, ctrl) 212 ctx := context.Background() 213 req := new(MockTStruct) 214 res := new(MockTStruct) 215 216 b.ResetTimer() 217 for i := 0; i < b.N; i++ { 218 cli.Call(ctx, mtd, req, res) 219 } 220 } 221 222 func BenchmarkCallParallel(b *testing.B) { 223 ctrl := gomock.NewController(b) 224 defer ctrl.Finish() 225 226 mtd := mocks.MockOnewayMethod 227 cli := newMockClient(b, ctrl) 228 ctx := context.Background() 229 req := new(MockTStruct) 230 res := new(MockTStruct) 231 232 b.ResetTimer() 233 b.RunParallel(func(pb *testing.PB) { 234 for pb.Next() { 235 cli.Call(ctx, mtd, req, res) 236 } 237 }) 238 } 239 240 func TestTagOptions(t *testing.T) { 241 ctrl := gomock.NewController(t) 242 defer ctrl.Finish() 243 244 mtd := mocks.MockMethod 245 tgs := map[string]string{} 246 cls := func(m map[string]string) { 247 for k := range m { 248 delete(m, k) 249 } 250 } 251 md := func(next endpoint.Endpoint) endpoint.Endpoint { 252 return func(ctx context.Context, req, res interface{}) error { 253 ri := rpcinfo.GetRPCInfo(ctx) 254 test.Assert(t, ri != nil) 255 256 to := ri.To() 257 test.Assert(t, to != nil) 258 259 re := remoteinfo.AsRemoteInfo(to) 260 test.Assert(t, re != nil) 261 262 for k, v := range tgs { 263 val, ok := re.Tag(k) 264 test.Assertf(t, ok, "expected tag not found: %s", k) 265 test.Assertf(t, v == val, "values of tag '%s' not equal: '%s' != '%s'", k, v, val) 266 } 267 return nil 268 } 269 } 270 var options []client.Option 271 options = append(options, WithMiddleware(md)) 272 273 ctx := context.Background() 274 req := new(MockTStruct) 275 res := new(MockTStruct) 276 277 cli := newMockClient(t, ctrl, options...) 278 err := cli.Call(ctx, mtd, req, res) 279 test.Assert(t, err == nil) 280 281 cls(tgs) 282 tgs["cluster"] = "client cluster" 283 tgs["idc"] = "client idc" 284 cli = newMockClient(t, ctrl, WithTag("cluster", "client cluster"), WithTag("idc", "client idc")) 285 err = cli.Call(ctx, mtd, req, res) 286 test.Assert(t, err == nil) 287 288 cls(tgs) 289 tgs["cluster"] = "call cluster" 290 tgs["idc"] = "call idc" 291 ctx = NewCtxWithCallOptions(ctx, []callopt.Option{ 292 callopt.WithTag("cluster", "cluster"), 293 callopt.WithTag("idc", "idc"), 294 }) 295 err = cli.Call(ctx, mtd, req, res) 296 test.Assert(t, err == nil) 297 } 298 299 func TestTagOptionLocks0(t *testing.T) { 300 ctrl := gomock.NewController(t) 301 defer ctrl.Finish() 302 303 mtd := mocks.MockMethod 304 md := func(next endpoint.Endpoint) endpoint.Endpoint { 305 return func(ctx context.Context, req, res interface{}) error { 306 re := remoteinfo.AsRemoteInfo(rpcinfo.GetRPCInfo(ctx).To()) 307 308 var err error 309 err = re.SetTag("cluster", "clusterx") 310 test.Assert(t, err == nil) 311 test.Assert(t, re.DefaultTag("cluster", "") == "clusterx") 312 313 err = re.SetTag("idc", "idcx") 314 test.Assert(t, err == nil) 315 test.Assert(t, re.DefaultTag("idc", "") == "idcx") 316 return nil 317 } 318 } 319 var options []client.Option 320 options = append(options, WithMiddleware(md)) 321 322 ctx := context.Background() 323 req := new(MockTStruct) 324 res := new(MockTStruct) 325 326 cli := newMockClient(t, ctrl, options...) 327 err := cli.Call(ctx, mtd, req, res) 328 test.Assert(t, err == nil) 329 } 330 331 func TestTagOptionLocks1(t *testing.T) { 332 ctrl := gomock.NewController(t) 333 defer ctrl.Finish() 334 335 mtd := mocks.MockMethod 336 md := func(next endpoint.Endpoint) endpoint.Endpoint { 337 return func(ctx context.Context, req, res interface{}) error { 338 re := remoteinfo.AsRemoteInfo(rpcinfo.GetRPCInfo(ctx).To()) 339 340 var err error 341 err = re.SetTag("cluster", "whatever") 342 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 343 344 err = re.SetTag("idc", "whatever") 345 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 346 return nil 347 } 348 } 349 var options []client.Option 350 options = append(options, WithMiddleware(md)) 351 options = append(options, WithTag("cluster", "client cluster")) 352 options = append(options, WithTag("idc", "client idc")) 353 354 ctx := context.Background() 355 req := new(MockTStruct) 356 res := new(MockTStruct) 357 358 cli := newMockClient(t, ctrl, options...) 359 err := cli.Call(ctx, mtd, req, res) 360 test.Assert(t, err == nil) 361 } 362 363 func TestTagOptionLocks2(t *testing.T) { 364 ctrl := gomock.NewController(t) 365 defer ctrl.Finish() 366 367 mtd := mocks.MockOnewayMethod 368 md := func(next endpoint.Endpoint) endpoint.Endpoint { 369 return func(ctx context.Context, req, res interface{}) error { 370 re := remoteinfo.AsRemoteInfo(rpcinfo.GetRPCInfo(ctx).To()) 371 372 var err error 373 err = re.SetTag("cluster", "whatever") 374 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 375 376 err = re.SetTag("idc", "whatever") 377 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 378 return nil 379 } 380 } 381 var options []client.Option 382 options = append(options, WithMiddleware(md)) 383 384 ctx := context.Background() 385 req := new(MockTStruct) 386 res := new(MockTStruct) 387 388 cli := newMockClient(t, ctrl, options...) 389 ctx = NewCtxWithCallOptions(ctx, []callopt.Option{ 390 callopt.WithTag("cluster", "cluster"), 391 callopt.WithTag("idc", "idc"), 392 }) 393 err := cli.Call(ctx, mtd, req, res) 394 test.Assert(t, err == nil) 395 } 396 397 func TestWarmingUpOption(t *testing.T) { 398 ctrl := gomock.NewController(t) 399 defer ctrl.Finish() 400 401 var options []client.Option 402 options = append(options, WithWarmingUp(mockWarmupOption)) 403 404 cli := newMockClient(t, ctrl, options...) 405 test.Assert(t, cli.(*kcFinalizerClient).opt.WarmUpOption == mockWarmupOption) 406 } 407 408 func TestTimeoutOptions(t *testing.T) { 409 ctrl := gomock.NewController(t) 410 defer ctrl.Finish() 411 412 mtd := mocks.MockMethod 413 md := func(next endpoint.Endpoint) endpoint.Endpoint { 414 return func(ctx context.Context, req, res interface{}) error { 415 ri := rpcinfo.GetRPCInfo(ctx) 416 test.Assert(t, ri != nil) 417 418 cfgs := ri.Config() 419 test.Assert(t, cfgs != nil) 420 421 mcfg := rpcinfo.AsMutableRPCConfig(cfgs) 422 test.Assert(t, mcfg != nil) 423 424 var err error 425 err = mcfg.SetRPCTimeout(time.Hour) 426 test.Assert(t, err == nil) 427 test.Assert(t, cfgs.RPCTimeout() == time.Hour) 428 429 err = mcfg.SetConnectTimeout(time.Hour * 2) 430 test.Assert(t, err == nil) 431 test.Assert(t, cfgs.ConnectTimeout() == time.Hour*2) 432 return nil 433 } 434 } 435 var options []client.Option 436 options = append(options, WithMiddleware(md)) 437 438 ctx := context.Background() 439 req := new(MockTStruct) 440 res := new(MockTStruct) 441 442 cli := newMockClient(t, ctrl, options...) 443 err := cli.Call(ctx, mtd, req, res) 444 test.Assert(t, err == nil) 445 } 446 447 func TestTimeoutCtxCall(t *testing.T) { 448 ctrl := gomock.NewController(t) 449 defer ctrl.Finish() 450 451 mtd := mocks.MockMethod 452 var accessed int32 453 md := func(next endpoint.Endpoint) endpoint.Endpoint { 454 return func(ctx context.Context, req, resp interface{}) (err error) { 455 time.Sleep(time.Millisecond * 100) 456 atomic.StoreInt32(&accessed, 1) 457 return next(ctx, req, resp) 458 } 459 } 460 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10) 461 req, res := new(MockTStruct), new(MockTStruct) 462 cli := newMockClient(t, ctrl, WithMiddleware(md)) 463 err := cli.Call(ctx, mtd, req, res) 464 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout)) 465 test.Assert(t, atomic.LoadInt32(&accessed) == 0) 466 cancel() 467 468 err = cli.Call(context.Background(), mtd, req, res) 469 test.Assert(t, err == nil) 470 test.Assert(t, atomic.LoadInt32(&accessed) == 1) 471 } 472 473 func TestTimeoutOptionsLock0(t *testing.T) { 474 ctrl := gomock.NewController(t) 475 defer ctrl.Finish() 476 477 tos := []time.Duration{time.Hour / 2, time.Hour / 3} 478 mtd := mocks.MockMethod 479 md := func(next endpoint.Endpoint) endpoint.Endpoint { 480 return func(ctx context.Context, req, res interface{}) error { 481 ri := rpcinfo.GetRPCInfo(ctx) 482 test.Assert(t, ri != nil) 483 484 cfgs := ri.Config() 485 test.Assert(t, cfgs != nil) 486 487 mcfg := rpcinfo.AsMutableRPCConfig(cfgs) 488 test.Assert(t, mcfg != nil) 489 490 var err error 491 err = mcfg.SetRPCTimeout(time.Hour) 492 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 493 test.Assert(t, cfgs.RPCTimeout() == tos[0]) 494 495 err = mcfg.SetConnectTimeout(time.Hour * 2) 496 test.Assert(t, errors.Is(err, kerrors.ErrNotSupported)) 497 test.Assert(t, cfgs.ConnectTimeout() == tos[1]) 498 return nil 499 } 500 } 501 var options []client.Option 502 options = append(options, WithMiddleware(md)) 503 options = append(options, WithRPCTimeout(tos[0])) 504 options = append(options, WithConnectTimeout(tos[1])) 505 506 ctx := context.Background() 507 req := new(MockTStruct) 508 res := new(MockTStruct) 509 510 cli := newMockClient(t, ctrl, options...) 511 err := cli.Call(ctx, mtd, req, res) 512 test.Assert(t, err == nil) 513 514 tos[0], tos[1] = time.Minute, time.Minute*2 515 ctx = NewCtxWithCallOptions(ctx, []callopt.Option{ 516 callopt.WithRPCTimeout(tos[0]), 517 callopt.WithConnectTimeout(tos[1]), 518 }) 519 err = cli.Call(ctx, mtd, req, res) 520 test.Assert(t, err == nil) 521 } 522 523 func TestAdjustTimeout(t *testing.T) { 524 ctrl := gomock.NewController(t) 525 defer ctrl.Finish() 526 527 adjustTimeoutMWB := func(moreTimeout time.Duration) endpoint.MiddlewareBuilder { 528 return func(mwCtx context.Context) endpoint.Middleware { 529 if p, ok := mwCtx.Value(rpctimeout.TimeoutAdjustKey).(*time.Duration); ok { 530 *p = moreTimeout 531 } 532 return endpoint.DummyMiddleware 533 } 534 } 535 mtd := mocks.MockMethod 536 md := func(next endpoint.Endpoint) endpoint.Endpoint { 537 return func(ctx context.Context, req, resp interface{}) (err error) { 538 time.Sleep(time.Second) 539 return nil 540 } 541 } 542 req, res := new(MockTStruct), new(MockTStruct) 543 544 // should timeout 545 cli := newMockClient(t, ctrl, 546 WithMiddlewareBuilder(adjustTimeoutMWB(0)), 547 WithMiddleware(md), 548 WithRPCTimeout(time.Millisecond*500)) 549 err := cli.Call(context.Background(), mtd, req, res) 550 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout)) 551 // shouldn't timeout 552 cli = newMockClient(t, ctrl, 553 WithMiddlewareBuilder(adjustTimeoutMWB(time.Second)), 554 WithMiddleware(md), 555 WithRPCTimeout(time.Millisecond*500)) 556 err = cli.Call(context.Background(), mtd, req, res) 557 test.Assert(t, err == nil, err) 558 } 559 560 func TestRetry(t *testing.T) { 561 ctrl := gomock.NewController(t) 562 defer ctrl.Finish() 563 564 var count int32 565 tp := rpc_info.NewMockTimeoutProvider(ctrl) 566 tp.EXPECT().Timeouts(gomock.Any()).DoAndReturn(func(ri rpcinfo.RPCInfo) rpcinfo.Timeouts { 567 retryTimeStr, ok := ri.To().Tag(rpcinfo.RetryTag) 568 if atomic.AddInt32(&count, 1) == 1 { 569 // first request 570 test.Assert(t, !ok) 571 } else { 572 // retry request 573 test.Assert(t, retryTimeStr == "1", retryTimeStr) 574 } 575 return ri.Config() 576 }).AnyTimes() 577 mockTimeoutMW := func(next endpoint.Endpoint) endpoint.Endpoint { 578 return func(ctx context.Context, req, resp interface{}) (err error) { 579 ri := rpcinfo.GetRPCInfo(ctx) 580 if atomic.LoadInt32(&count) == 1 { 581 return kerrors.ErrRPCTimeout 582 } else { 583 retryTimeStr := ri.To().DefaultTag(rpcinfo.RetryTag, "0") 584 test.Assert(t, retryTimeStr == "1", retryTimeStr) 585 r, _ := metainfo.GetPersistentValue(ctx, retry.TransitKey) 586 test.Assert(t, r == "1", r) 587 } 588 return nil 589 } 590 } 591 592 // case1: return timeout 593 cli := newMockClient(t, ctrl, 594 WithTimeoutProvider(tp), 595 WithMiddleware(mockTimeoutMW)) 596 mtd := mocks.MockMethod 597 req, res := new(MockTStruct), new(MockTStruct) 598 err := cli.Call(context.Background(), mtd, req, res) 599 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout)) 600 test.Assert(t, count == 1, count) 601 602 // case 2: have timeout retry, return success 603 count = 0 604 cli = newMockClient(t, ctrl, 605 WithTimeoutProvider(tp), 606 WithMiddleware(mockTimeoutMW), 607 WithFailureRetry(&retry.FailurePolicy{ 608 StopPolicy: retry.StopPolicy{ 609 MaxRetryTimes: 3, 610 CBPolicy: retry.CBPolicy{ 611 ErrorRate: 0.1, 612 }, 613 }, 614 RetrySameNode: true, 615 })) 616 err = cli.Call(context.Background(), mtd, req, res) 617 test.Assert(t, err == nil, err) 618 test.Assert(t, count == 2, count) 619 } 620 621 func TestRetryWithDifferentTimeout(t *testing.T) { 622 ctrl := gomock.NewController(t) 623 defer ctrl.Finish() 624 625 var count int32 626 tp := rpc_info.NewMockTimeoutProvider(ctrl) 627 tp.EXPECT().Timeouts(gomock.Any()).DoAndReturn(func(ri rpcinfo.RPCInfo) rpcinfo.Timeouts { 628 cfg := rpcinfo.AsMutableRPCConfig(ri.Config()).Clone() 629 retryTimeStr, ok := ri.To().Tag(rpcinfo.RetryTag) 630 if atomic.AddInt32(&count, 1) == 1 { 631 // first request 632 test.Assert(t, !ok) 633 cfg.SetRPCTimeout(100 * time.Millisecond) 634 } else { 635 // retry request 636 test.Assert(t, retryTimeStr == "1", retryTimeStr) 637 // reset rpc timeout for retry request 638 cfg.SetRPCTimeout(500 * time.Millisecond) 639 } 640 return cfg.ImmutableView() 641 }).AnyTimes() 642 mockTimeoutMW := func(next endpoint.Endpoint) endpoint.Endpoint { 643 return func(ctx context.Context, req, resp interface{}) (err error) { 644 ri := rpcinfo.GetRPCInfo(ctx) 645 if atomic.LoadInt32(&count) == 1 { 646 test.Assert(t, ri.Config().RPCTimeout().Milliseconds() == 100, ri.Config().RPCTimeout().Milliseconds()) 647 } else { 648 retryTimeStr := ri.To().DefaultTag(rpcinfo.RetryTag, "0") 649 test.Assert(t, retryTimeStr == "1", retryTimeStr) 650 r, _ := metainfo.GetPersistentValue(ctx, retry.TransitKey) 651 test.Assert(t, r == "1", r) 652 test.Assert(t, ri.Config().RPCTimeout().Milliseconds() == 500, ri.Config().RPCTimeout().Milliseconds()) 653 } 654 time.Sleep(200 * time.Millisecond) 655 return nil 656 } 657 } 658 659 // case: sleep 200ms 660 // first request timeout is 100 then the timeout happens 661 // retry request timeout is 500 then success 662 cli := newMockClient(t, ctrl, 663 WithTimeoutProvider(tp), 664 WithMiddleware(mockTimeoutMW), 665 WithFailureRetry(&retry.FailurePolicy{ 666 StopPolicy: retry.StopPolicy{ 667 MaxRetryTimes: 3, 668 CBPolicy: retry.CBPolicy{ 669 ErrorRate: 0.1, 670 }, 671 }, 672 RetrySameNode: true, 673 })) 674 mtd := mocks.MockMethod 675 req, res := new(MockTStruct), new(MockTStruct) 676 err := cli.Call(context.Background(), mtd, req, res) 677 test.Assert(t, err == nil, err) 678 test.Assert(t, count == 2, count) 679 } 680 681 func TestRetryWithResultRetry(t *testing.T) { 682 ctrl := gomock.NewController(t) 683 defer ctrl.Finish() 684 685 mockErr := errors.New("mock") 686 retryWithMockErr := false 687 errMW := func(next endpoint.Endpoint) endpoint.Endpoint { 688 var count int32 689 return func(ctx context.Context, req, resp interface{}) (err error) { 690 if atomic.CompareAndSwapInt32(&count, 0, 1) { 691 return mockErr 692 } 693 return nil 694 } 695 } 696 mockTimeoutMW := func(next endpoint.Endpoint) endpoint.Endpoint { 697 var count int32 698 return func(ctx context.Context, req, resp interface{}) (err error) { 699 if atomic.CompareAndSwapInt32(&count, 0, 1) { 700 time.Sleep(300 * time.Millisecond) 701 } 702 return nil 703 } 704 } 705 errRetryFunc := func(err error, ri rpcinfo.RPCInfo) bool { 706 if errors.Is(err, mockErr) { 707 retryWithMockErr = true 708 return true 709 } 710 return false 711 } 712 713 // case 1: retry for mockErr 714 cli := newMockClient(t, ctrl, 715 WithMiddleware(errMW), 716 WithRPCTimeout(100*time.Millisecond), 717 WithFailureRetry(&retry.FailurePolicy{ 718 StopPolicy: retry.StopPolicy{ 719 MaxRetryTimes: 3, 720 CBPolicy: retry.CBPolicy{ 721 ErrorRate: 0.1, 722 }, 723 }, 724 RetrySameNode: true, 725 ShouldResultRetry: &retry.ShouldResultRetry{ErrorRetry: errRetryFunc}, 726 })) 727 mtd := mocks.MockMethod 728 req, res := new(MockTStruct), new(MockTStruct) 729 err := cli.Call(context.Background(), mtd, req, res) 730 test.Assert(t, err == nil, err) 731 test.Assert(t, retryWithMockErr) 732 733 // case 1: retry for timeout 734 cli = newMockClient(t, ctrl, 735 WithMiddleware(mockTimeoutMW), 736 WithRPCTimeout(100*time.Millisecond), 737 WithFailureRetry(&retry.FailurePolicy{ 738 StopPolicy: retry.StopPolicy{ 739 MaxRetryTimes: 3, 740 CBPolicy: retry.CBPolicy{ 741 ErrorRate: 0.1, 742 }, 743 }, 744 RetrySameNode: true, 745 ShouldResultRetry: &retry.ShouldResultRetry{ErrorRetry: errRetryFunc}, 746 })) 747 err = cli.Call(context.Background(), mtd, req, res) 748 test.Assert(t, err == nil, err) 749 750 // case 2: set NotRetryForTimeout as true, won't do retry for timeout 751 cli = newMockClient(t, ctrl, 752 WithMiddleware(mockTimeoutMW), 753 WithRPCTimeout(100*time.Millisecond), 754 WithFailureRetry(&retry.FailurePolicy{ 755 StopPolicy: retry.StopPolicy{ 756 MaxRetryTimes: 3, 757 CBPolicy: retry.CBPolicy{ 758 ErrorRate: 0.1, 759 }, 760 }, 761 RetrySameNode: true, 762 ShouldResultRetry: &retry.ShouldResultRetry{ErrorRetry: errRetryFunc, NotRetryForTimeout: true}, 763 })) 764 err = cli.Call(context.Background(), mtd, req, res) 765 test.Assert(t, errors.Is(err, kerrors.ErrRPCTimeout)) 766 } 767 768 func TestFallbackForError(t *testing.T) { 769 ctrl := gomock.NewController(t) 770 defer ctrl.Finish() 771 772 isTimeout := false 773 isMockErr := false 774 isBizErr := false 775 errForReportIsNil := false 776 var rpcResult interface{} 777 778 // prepare mock data 779 retStr := "success" 780 sleepMW := func(next endpoint.Endpoint) endpoint.Endpoint { 781 return func(ctx context.Context, req, resp interface{}) (err error) { 782 time.Sleep(300 * time.Millisecond) 783 return nil 784 } 785 } 786 mockErr := errors.New("mock") 787 mockErrMW := func(next endpoint.Endpoint) endpoint.Endpoint { 788 return func(ctx context.Context, req, resp interface{}) (err error) { 789 return mockErr 790 } 791 } 792 bizErrMW := func(next endpoint.Endpoint) endpoint.Endpoint { 793 return func(ctx context.Context, req, resp interface{}) (err error) { 794 if setter, ok := rpcinfo.GetRPCInfo(ctx).Invocation().(rpcinfo.InvocationSetter); ok { 795 setter.SetBizStatusErr(kerrors.NewBizStatusError(100, mockErr.Error())) 796 } 797 return nil 798 } 799 } 800 801 mockTracer := mocksstats.NewMockTracer(ctrl) 802 mockTracer.EXPECT().Start(gomock.Any()).DoAndReturn(func(ctx context.Context) context.Context { 803 return ctx 804 }).AnyTimes() 805 mockTracer.EXPECT().Finish(gomock.Any()).DoAndReturn(func(ctx context.Context) { 806 if rpcinfo.GetRPCInfo(ctx).Stats().Error() == nil { 807 errForReportIsNil = true 808 } else { 809 errForReportIsNil = false 810 } 811 }).AnyTimes() 812 newFallback := func(realReqResp bool, testCase string) *fallback.Policy { 813 var fallbackFunc fallback.Func 814 checkErr := func(err error) { 815 isTimeout, isMockErr, isBizErr = false, false, false 816 if errors.Is(err, kerrors.ErrRPCTimeout) { 817 isTimeout = true 818 } 819 if errors.Is(err, mockErr) { 820 isMockErr = true 821 } 822 if _, ok := kerrors.FromBizStatusError(err); ok { 823 isBizErr = true 824 } 825 } 826 if realReqResp { 827 fallbackFunc = fallback.UnwrapHelper(func(ctx context.Context, req, resp interface{}, err error) (fbResp interface{}, fbErr error) { 828 checkErr(err) 829 _, ok := req.(*mockthrift.MockReq) 830 test.Assert(t, ok, testCase) 831 return &retStr, nil 832 }) 833 } else { 834 fallbackFunc = func(ctx context.Context, args utils.KitexArgs, result utils.KitexResult, err error) (fbErr error) { 835 checkErr(err) 836 _, ok := args.(*mockthrift.MockTestArgs) 837 test.Assert(t, ok, testCase) 838 result.SetSuccess(&retStr) 839 return nil 840 } 841 } 842 return fallback.NewFallbackPolicy(fallbackFunc) 843 } 844 845 // case 1: fallback for timeout, return nil err, but report original err 846 cli := newMockClient(t, ctrl, 847 WithMiddleware(sleepMW), 848 WithRPCTimeout(100*time.Millisecond), 849 WithFallback(newFallback(false, "case 1")), 850 WithTracer(mockTracer), 851 ) 852 mtd := mocks.MockMethod 853 rpcResult = mockthrift.NewMockTestResult() 854 err := cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 855 test.Assert(t, err == nil, err) 856 test.Assert(t, *rpcResult.(utils.KitexResult).GetResult().(*string) == retStr) 857 test.Assert(t, isTimeout, err) 858 test.Assert(t, !errForReportIsNil) 859 860 // case 2: fallback for mock error, but report original err 861 cli = newMockClient(t, ctrl, 862 WithMiddleware(mockErrMW), 863 WithFallback(newFallback(false, "case 2"))) 864 rpcResult = mockthrift.NewMockTestResult() 865 err = cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 866 test.Assert(t, err == nil, err) 867 test.Assert(t, *rpcResult.(utils.KitexResult).GetResult().(*string) == retStr) 868 test.Assert(t, isMockErr, err) 869 test.Assert(t, !errForReportIsNil) 870 871 // case 3: fallback for timeout, return nil err, and report nil err as enable reportAsFallback 872 cli = newMockClient(t, ctrl, 873 WithMiddleware(sleepMW), 874 WithRPCTimeout(100*time.Millisecond), 875 WithFallback(newFallback(true, "case 3").EnableReportAsFallback()), 876 WithTracer(mockTracer), 877 ) 878 // reset 879 isTimeout = false 880 errForReportIsNil = false 881 rpcResult = mockthrift.NewMockTestResult() 882 err = cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 883 test.Assert(t, err == nil, err) 884 test.Assert(t, *rpcResult.(utils.KitexResult).GetResult().(*string) == retStr) 885 test.Assert(t, isTimeout, err) 886 test.Assert(t, errForReportIsNil) 887 888 // case 4: no fallback, return biz err, but report nil err 889 cli = newMockClient(t, ctrl, 890 WithMiddleware(bizErrMW), 891 WithRPCTimeout(100*time.Millisecond), 892 WithTracer(mockTracer), 893 ) 894 // reset 895 errForReportIsNil = false 896 rpcResult = mockthrift.NewMockTestResult() 897 err = cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 898 _, ok := kerrors.FromBizStatusError(err) 899 test.Assert(t, ok, err) 900 test.Assert(t, errForReportIsNil) 901 902 // case 5: fallback for biz error, return nil err, 903 // and report nil err even if don't enable reportAsFallback as biz error won't report failure by design 904 cli = newMockClient(t, ctrl, 905 WithMiddleware(bizErrMW), 906 WithRPCTimeout(100*time.Millisecond), 907 WithFallback(newFallback(true, "case 5")), 908 WithTracer(mockTracer), 909 ) 910 // reset 911 isBizErr = false 912 errForReportIsNil = false 913 rpcResult = mockthrift.NewMockTestResult() 914 err = cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 915 test.Assert(t, err == nil, err) 916 test.Assert(t, *rpcResult.(utils.KitexResult).GetResult().(*string) == retStr) 917 test.Assert(t, isBizErr, err) 918 test.Assert(t, errForReportIsNil) 919 920 // case 6: fallback for timeout error, return nil err 921 cli = newMockClient(t, ctrl, 922 WithMiddleware(sleepMW), 923 WithRPCTimeout(100*time.Millisecond), 924 WithFallback(fallback.TimeoutAndCBFallback(fallback.UnwrapHelper(func(ctx context.Context, req, resp interface{}, err error) (fbResp interface{}, fbErr error) { 925 if errors.Is(err, kerrors.ErrRPCTimeout) { 926 isTimeout = true 927 } 928 return &retStr, nil 929 }))), 930 WithTracer(mockTracer), 931 ) 932 // reset 933 isTimeout = false 934 errForReportIsNil = true 935 rpcResult = mockthrift.NewMockTestResult() 936 err = cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 937 test.Assert(t, err == nil, err) 938 test.Assert(t, *rpcResult.(utils.KitexResult).GetResult().(*string) == retStr, rpcResult) 939 test.Assert(t, isTimeout, err) 940 test.Assert(t, !errForReportIsNil) 941 942 // case 6: use TimeoutAndCBFallback, non-timeout and non-CB error cannot do fallback 943 var fallbackExecuted bool 944 cli = newMockClient(t, ctrl, 945 WithMiddleware(mockErrMW), 946 WithRPCTimeout(100*time.Millisecond), 947 WithFallback(fallback.TimeoutAndCBFallback(func(ctx context.Context, args utils.KitexArgs, result utils.KitexResult, err error) (fbErr error) { 948 fallbackExecuted = true 949 return 950 })), 951 WithTracer(mockTracer), 952 ) 953 // reset 954 errForReportIsNil = true 955 rpcResult = mockthrift.NewMockTestResult() 956 err = cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 957 test.Assert(t, err != nil, err) 958 test.Assert(t, !fallbackExecuted) 959 960 // case 7: fallback return nil-resp and nil-err, framework will return the original resp and err 961 cli = newMockClient(t, ctrl, 962 WithMiddleware(mockErrMW), 963 WithRPCTimeout(100*time.Millisecond), 964 WithFallback(fallback.NewFallbackPolicy(func(ctx context.Context, args utils.KitexArgs, result utils.KitexResult, err error) (fbErr error) { 965 return 966 })), 967 WithTracer(mockTracer), 968 ) 969 mtd = mocks.MockMethod 970 rpcResult = mockthrift.NewMockTestResult() 971 err = cli.Call(context.Background(), mtd, mockthrift.NewMockTestArgs(), rpcResult) 972 test.Assert(t, err == mockErr) 973 test.Assert(t, !errForReportIsNil) 974 } 975 976 func TestClientFinalizer(t *testing.T) { 977 ctrl := gomock.NewController(t) 978 defer ctrl.Finish() 979 980 debug.SetGCPercent(-1) 981 defer debug.SetGCPercent(100) 982 983 var ms runtime.MemStats 984 runtime.ReadMemStats(&ms) 985 t.Logf("Before new clients, allocation: %f Mb, Number of allocation: %d\n", mb(ms.HeapAlloc), ms.HeapObjects) 986 987 var ( 988 closeCalledCnt int32 989 succeedCnt = 10000 990 failedCnt = 10000 991 cliCnt = succeedCnt + failedCnt 992 ) 993 clis := make([]Client, cliCnt) 994 // clients that init successfully. 995 for i := 0; i < succeedCnt; i++ { 996 svcInfo := mocks.ServiceInfo() 997 mockClient, err := NewClient(svcInfo, WithDestService("destService"), WithShortConnection(), 998 WithCloseCallbacks(func() error { 999 atomic.AddInt32(&closeCalledCnt, 1) 1000 return nil 1001 })) 1002 test.Assert(t, err == nil, err) 1003 clis[i] = mockClient 1004 } 1005 // clients that init failed, closeCallback should be called 1006 for i := succeedCnt; i < cliCnt; i++ { 1007 mockClient, err := NewClient(svcInfo, WithDestService(""), WithShortConnection(), 1008 WithCloseCallbacks(func() error { 1009 atomic.AddInt32(&closeCalledCnt, 1) 1010 return nil 1011 })) 1012 test.Assert(t, err != nil, err) 1013 clis[i] = mockClient 1014 } 1015 1016 runtime.ReadMemStats(&ms) 1017 t.Logf("After new clients, allocation: %f Mb, Number of allocation: %d\n", mb(ms.HeapAlloc), ms.HeapObjects) 1018 1019 runtime.GC() 1020 runtime.ReadMemStats(&ms) 1021 firstGCHeapAlloc, firstGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects 1022 t.Logf("After first GC, allocation: %f Mb, Number of allocation: %d\n", firstGCHeapAlloc, firstGCHeapObjects) 1023 time.Sleep(200 * time.Millisecond) // ensure the finalizer be executed 1024 test.Assert(t, atomic.LoadInt32(&closeCalledCnt) == int32(cliCnt)) // ensure CloseCallback of client has been called 1025 1026 runtime.GC() 1027 runtime.ReadMemStats(&ms) 1028 secondGCHeapAlloc, secondGCHeapObjects := mb(ms.HeapAlloc), ms.HeapObjects 1029 t.Logf("After second GC, allocation: %f Mb, Number of allocation: %d\n", secondGCHeapAlloc, secondGCHeapObjects) 1030 test.Assert(t, secondGCHeapAlloc < firstGCHeapAlloc/2 && secondGCHeapObjects < firstGCHeapObjects/2) 1031 } 1032 1033 func TestPanicInMiddleware(t *testing.T) { 1034 ctrl := gomock.NewController(t) 1035 defer ctrl.Finish() 1036 1037 reportPanic := false 1038 mockTracer := mocksstats.NewMockTracer(ctrl) 1039 mockTracer.EXPECT().Start(gomock.Any()).DoAndReturn(func(ctx context.Context) context.Context { 1040 return ctx 1041 }).AnyTimes() 1042 mockTracer.EXPECT().Finish(gomock.Any()).DoAndReturn(func(ctx context.Context) { 1043 if ok, _ := rpcinfo.GetRPCInfo(ctx).Stats().Panicked(); ok { 1044 reportPanic = true 1045 } 1046 }).AnyTimes() 1047 panicMW := func(endpoint.Endpoint) endpoint.Endpoint { 1048 return func(ctx context.Context, request, response interface{}) (err error) { 1049 panic(panicMsg) 1050 } 1051 } 1052 1053 // panic with timeout, the panic is recovered in timeout mw 1054 cli := newMockClient(t, ctrl, 1055 WithMiddleware(panicMW), 1056 WithRPCTimeout(100*time.Millisecond), 1057 WithTracer(mockTracer)) 1058 mtd := mocks.MockMethod 1059 req, res := new(MockTStruct), new(MockTStruct) 1060 err := cli.Call(context.Background(), mtd, req, res) 1061 test.Assert(t, err != nil) 1062 test.Assert(t, strings.Contains(err.Error(), panicMsg), err) 1063 test.Assert(t, reportPanic, err) 1064 1065 // panic without timeout, the panic is recovered in client.Call 1066 reportPanic = false 1067 cli = newMockClient(t, ctrl, 1068 WithMiddleware(panicMW), 1069 WithRPCTimeout(0), 1070 WithTracer(mockTracer)) 1071 req, res = new(MockTStruct), new(MockTStruct) 1072 err = cli.Call(context.Background(), mtd, req, res) 1073 test.Assert(t, err != nil) 1074 test.Assert(t, strings.Contains(err.Error(), panicMsg), err) 1075 test.Assert(t, reportPanic, err) 1076 } 1077 1078 func TestMethodInfoIsNil(t *testing.T) { 1079 ctrl := gomock.NewController(t) 1080 defer ctrl.Finish() 1081 cli := newMockClient(t, ctrl) 1082 req, res := new(MockTStruct), new(MockTStruct) 1083 err := cli.Call(context.Background(), "notExist", req, res) 1084 test.Assert(t, err != nil) 1085 test.Assert(t, strings.Contains(err.Error(), "method info is nil")) 1086 } 1087 1088 func mb(byteSize uint64) float32 { 1089 return float32(byteSize) / float32(1024*1024) 1090 } 1091 1092 func Test_initTransportProtocol(t *testing.T) { 1093 t.Run("kitex_pb", func(t *testing.T) { 1094 svcInfo := &serviceinfo.ServiceInfo{ 1095 PayloadCodec: serviceinfo.Protobuf, 1096 } 1097 rpcConfig := rpcinfo.NewRPCConfig() 1098 1099 initTransportProtocol(svcInfo, rpcConfig) 1100 test.Assert(t, rpcConfig.PayloadCodec() == serviceinfo.Protobuf, rpcConfig.PayloadCodec()) 1101 test.Assert(t, rpcConfig.TransportProtocol() == transport.TTHeaderFramed, rpcConfig.TransportProtocol()) 1102 }) 1103 1104 t.Run("grpc_pb", func(t *testing.T) { 1105 svcInfo := &serviceinfo.ServiceInfo{ 1106 PayloadCodec: serviceinfo.Protobuf, 1107 } 1108 rpcConfig := rpcinfo.NewRPCConfig() 1109 rpcinfo.AsMutableRPCConfig(rpcConfig).SetTransportProtocol(transport.GRPC) 1110 1111 initTransportProtocol(svcInfo, rpcConfig) 1112 test.Assert(t, rpcConfig.PayloadCodec() == serviceinfo.Protobuf, rpcConfig.PayloadCodec()) 1113 test.Assert(t, rpcConfig.TransportProtocol() == transport.GRPC, rpcConfig.TransportProtocol()) 1114 }) 1115 1116 t.Run("grpc_thrift", func(t *testing.T) { 1117 svcInfo := &serviceinfo.ServiceInfo{ 1118 PayloadCodec: serviceinfo.Thrift, 1119 } 1120 rpcConfig := rpcinfo.NewRPCConfig() 1121 rpcinfo.AsMutableRPCConfig(rpcConfig).SetTransportProtocol(transport.GRPC) 1122 1123 initTransportProtocol(svcInfo, rpcConfig) 1124 test.Assert(t, rpcConfig.PayloadCodec() == serviceinfo.Thrift, rpcConfig.PayloadCodec()) 1125 test.Assert(t, rpcConfig.TransportProtocol() == transport.GRPC, rpcConfig.TransportProtocol()) 1126 }) 1127 } 1128 1129 type mockTracerForInitMW struct { 1130 stats.Tracer 1131 rpcinfo.StreamEventReporter 1132 } 1133 1134 func Test_kClient_initStreamMiddlewares(t *testing.T) { 1135 ctl := &rpcinfo.TraceController{} 1136 ctl.Append(mockTracerForInitMW{}) 1137 c := &kClient{ 1138 opt: &client.Options{ 1139 TracerCtl: ctl, 1140 Streaming: internal_stream.StreamingConfig{}, 1141 }, 1142 } 1143 c.initStreamMiddlewares(context.Background()) 1144 1145 test.Assert(t, len(c.opt.Streaming.RecvMiddlewares) > 0, "init middlewares failed") 1146 test.Assert(t, len(c.opt.Streaming.SendMiddlewares) > 0, "init middlewares failed") 1147 }