gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/grpc/balancer/roundrobin/roundrobin_test.go (about) 1 /* 2 * 3 * Copyright 2017 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package roundrobin_test 20 21 import ( 22 "context" 23 "fmt" 24 "net" 25 "strings" 26 "sync" 27 "testing" 28 "time" 29 30 grpc "gitee.com/ks-custle/core-gm/grpc" 31 "gitee.com/ks-custle/core-gm/grpc/balancer/roundrobin" 32 "gitee.com/ks-custle/core-gm/grpc/codes" 33 "gitee.com/ks-custle/core-gm/grpc/connectivity" 34 "gitee.com/ks-custle/core-gm/grpc/credentials/insecure" 35 "gitee.com/ks-custle/core-gm/grpc/internal/grpctest" 36 imetadata "gitee.com/ks-custle/core-gm/grpc/internal/metadata" 37 "gitee.com/ks-custle/core-gm/grpc/metadata" 38 "gitee.com/ks-custle/core-gm/grpc/peer" 39 "gitee.com/ks-custle/core-gm/grpc/resolver" 40 "gitee.com/ks-custle/core-gm/grpc/resolver/manual" 41 "gitee.com/ks-custle/core-gm/grpc/status" 42 testpb "gitee.com/ks-custle/core-gm/grpc/test/grpc_testing" 43 ) 44 45 const ( 46 testMDKey = "test-md" 47 ) 48 49 type s struct { 50 grpctest.Tester 51 } 52 53 func Test(t *testing.T) { 54 grpctest.RunSubTests(t, s{}) 55 } 56 57 type testServer struct { 58 testpb.UnimplementedTestServiceServer 59 60 testMDChan chan []string 61 } 62 63 func newTestServer(mdchan bool) *testServer { 64 t := &testServer{} 65 if mdchan { 66 t.testMDChan = make(chan []string, 1) 67 } 68 return t 69 } 70 71 func (s *testServer) EmptyCall(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) { 72 if s.testMDChan == nil { 73 return &testpb.Empty{}, nil 74 } 75 md, ok := metadata.FromIncomingContext(ctx) 76 if !ok { 77 return nil, status.Errorf(codes.Internal, "no metadata in context") 78 } 79 select { 80 case s.testMDChan <- md[testMDKey]: 81 case <-ctx.Done(): 82 return nil, ctx.Err() 83 } 84 return &testpb.Empty{}, nil 85 } 86 87 func (s *testServer) FullDuplexCall(stream testpb.TestService_FullDuplexCallServer) error { 88 return nil 89 } 90 91 type test struct { 92 servers []*grpc.Server 93 serverImpls []*testServer 94 addresses []string 95 } 96 97 func (t *test) cleanup() { 98 for _, s := range t.servers { 99 s.Stop() 100 } 101 } 102 103 func startTestServers(count int, mdchan bool) (_ *test, err error) { 104 t := &test{} 105 106 defer func() { 107 if err != nil { 108 t.cleanup() 109 } 110 }() 111 for i := 0; i < count; i++ { 112 lis, err := net.Listen("tcp", "localhost:0") 113 if err != nil { 114 return nil, fmt.Errorf("failed to listen %v", err) 115 } 116 117 s := grpc.NewServer() 118 sImpl := newTestServer(mdchan) 119 testpb.RegisterTestServiceServer(s, sImpl) 120 t.servers = append(t.servers, s) 121 t.serverImpls = append(t.serverImpls, sImpl) 122 t.addresses = append(t.addresses, lis.Addr().String()) 123 124 go func(s *grpc.Server, l net.Listener) { 125 s.Serve(l) 126 }(s, lis) 127 } 128 129 return t, nil 130 } 131 132 func (s) TestOneBackend(t *testing.T) { 133 r := manual.NewBuilderWithScheme("whatever") 134 135 test, err := startTestServers(1, false) 136 if err != nil { 137 t.Fatalf("failed to start servers: %v", err) 138 } 139 defer test.cleanup() 140 cc, err := grpc.Dial(r.Scheme()+":///test.server", 141 grpc.WithTransportCredentials(insecure.NewCredentials()), 142 grpc.WithResolvers(r), 143 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 144 if err != nil { 145 t.Fatalf("failed to dial: %v", err) 146 } 147 defer cc.Close() 148 testc := testpb.NewTestServiceClient(cc) 149 // The first RPC should fail because there's no address. 150 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 151 defer cancel() 152 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded { 153 t.Fatalf("EmptyCall() = _, %v, want _, DeadlineExceeded", err) 154 } 155 156 r.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: test.addresses[0]}}}) 157 // The second RPC should succeed. 158 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}); err != nil { 159 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 160 } 161 } 162 163 func (s) TestBackendsRoundRobin(t *testing.T) { 164 r := manual.NewBuilderWithScheme("whatever") 165 166 backendCount := 5 167 test, err := startTestServers(backendCount, false) 168 if err != nil { 169 t.Fatalf("failed to start servers: %v", err) 170 } 171 defer test.cleanup() 172 173 cc, err := grpc.Dial(r.Scheme()+":///test.server", 174 grpc.WithTransportCredentials(insecure.NewCredentials()), 175 grpc.WithResolvers(r), 176 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 177 if err != nil { 178 t.Fatalf("failed to dial: %v", err) 179 } 180 defer cc.Close() 181 testc := testpb.NewTestServiceClient(cc) 182 // The first RPC should fail because there's no address. 183 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 184 defer cancel() 185 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded { 186 t.Fatalf("EmptyCall() = _, %v, want _, DeadlineExceeded", err) 187 } 188 189 var resolvedAddrs []resolver.Address 190 for i := 0; i < backendCount; i++ { 191 resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: test.addresses[i]}) 192 } 193 194 r.UpdateState(resolver.State{Addresses: resolvedAddrs}) 195 var p peer.Peer 196 // Make sure connections to all servers are up. 197 for si := 0; si < backendCount; si++ { 198 var connected bool 199 for i := 0; i < 1000; i++ { 200 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 201 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 202 } 203 if p.Addr.String() == test.addresses[si] { 204 connected = true 205 break 206 } 207 time.Sleep(time.Millisecond) 208 } 209 if !connected { 210 t.Fatalf("Connection to %v was not up after more than 1 second", test.addresses[si]) 211 } 212 } 213 214 for i := 0; i < 3*backendCount; i++ { 215 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 216 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 217 } 218 if p.Addr.String() != test.addresses[i%backendCount] { 219 t.Fatalf("Index %d: want peer %v, got peer %v", i, test.addresses[i%backendCount], p.Addr.String()) 220 } 221 } 222 } 223 224 func (s) TestAddressesRemoved(t *testing.T) { 225 r := manual.NewBuilderWithScheme("whatever") 226 227 test, err := startTestServers(1, false) 228 if err != nil { 229 t.Fatalf("failed to start servers: %v", err) 230 } 231 defer test.cleanup() 232 233 cc, err := grpc.Dial(r.Scheme()+":///test.server", 234 grpc.WithTransportCredentials(insecure.NewCredentials()), 235 grpc.WithResolvers(r), 236 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 237 if err != nil { 238 t.Fatalf("failed to dial: %v", err) 239 } 240 defer cc.Close() 241 testc := testpb.NewTestServiceClient(cc) 242 // The first RPC should fail because there's no address. 243 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 244 defer cancel() 245 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded { 246 t.Fatalf("EmptyCall() = _, %v, want _, DeadlineExceeded", err) 247 } 248 249 r.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: test.addresses[0]}}}) 250 // The second RPC should succeed. 251 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}); err != nil { 252 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 253 } 254 255 r.UpdateState(resolver.State{Addresses: []resolver.Address{}}) 256 257 ctx2, cancel2 := context.WithTimeout(context.Background(), 500*time.Millisecond) 258 defer cancel2() 259 // Wait for state to change to transient failure. 260 for src := cc.GetState(); src != connectivity.TransientFailure; src = cc.GetState() { 261 if !cc.WaitForStateChange(ctx2, src) { 262 t.Fatalf("timed out waiting for state change. got %v; want %v", src, connectivity.TransientFailure) 263 } 264 } 265 266 const msgWant = "produced zero addresses" 267 if _, err := testc.EmptyCall(ctx2, &testpb.Empty{}); !strings.Contains(status.Convert(err).Message(), msgWant) { 268 t.Fatalf("EmptyCall() = _, %v, want _, Contains(Message(), %q)", err, msgWant) 269 } 270 } 271 272 func (s) TestCloseWithPendingRPC(t *testing.T) { 273 r := manual.NewBuilderWithScheme("whatever") 274 275 test, err := startTestServers(1, false) 276 if err != nil { 277 t.Fatalf("failed to start servers: %v", err) 278 } 279 defer test.cleanup() 280 281 cc, err := grpc.Dial(r.Scheme()+":///test.server", 282 grpc.WithTransportCredentials(insecure.NewCredentials()), 283 grpc.WithResolvers(r), 284 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 285 if err != nil { 286 t.Fatalf("failed to dial: %v", err) 287 } 288 testc := testpb.NewTestServiceClient(cc) 289 290 var wg sync.WaitGroup 291 for i := 0; i < 3; i++ { 292 wg.Add(1) 293 go func() { 294 defer wg.Done() 295 // This RPC blocks until cc is closed. 296 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 297 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) == codes.DeadlineExceeded { 298 t.Errorf("RPC failed because of deadline after cc is closed; want error the client connection is closing") 299 } 300 cancel() 301 }() 302 } 303 cc.Close() 304 wg.Wait() 305 } 306 307 func (s) TestNewAddressWhileBlocking(t *testing.T) { 308 r := manual.NewBuilderWithScheme("whatever") 309 310 test, err := startTestServers(1, false) 311 if err != nil { 312 t.Fatalf("failed to start servers: %v", err) 313 } 314 defer test.cleanup() 315 316 cc, err := grpc.Dial(r.Scheme()+":///test.server", 317 grpc.WithTransportCredentials(insecure.NewCredentials()), 318 grpc.WithResolvers(r), 319 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 320 if err != nil { 321 t.Fatalf("failed to dial: %v", err) 322 } 323 defer cc.Close() 324 testc := testpb.NewTestServiceClient(cc) 325 // The first RPC should fail because there's no address. 326 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 327 defer cancel() 328 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded { 329 t.Fatalf("EmptyCall() = _, %v, want _, DeadlineExceeded", err) 330 } 331 332 r.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: test.addresses[0]}}}) 333 // The second RPC should succeed. 334 ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second) 335 defer cancel() 336 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); err != nil { 337 t.Fatalf("EmptyCall() = _, %v, want _, nil", err) 338 } 339 340 r.UpdateState(resolver.State{Addresses: []resolver.Address{}}) 341 342 var wg sync.WaitGroup 343 for i := 0; i < 3; i++ { 344 wg.Add(1) 345 go func() { 346 defer wg.Done() 347 // This RPC blocks until NewAddress is called. 348 testc.EmptyCall(context.Background(), &testpb.Empty{}) 349 }() 350 } 351 time.Sleep(50 * time.Millisecond) 352 r.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: test.addresses[0]}}}) 353 wg.Wait() 354 } 355 356 func (s) TestOneServerDown(t *testing.T) { 357 r := manual.NewBuilderWithScheme("whatever") 358 359 backendCount := 3 360 test, err := startTestServers(backendCount, false) 361 if err != nil { 362 t.Fatalf("failed to start servers: %v", err) 363 } 364 defer test.cleanup() 365 366 cc, err := grpc.Dial(r.Scheme()+":///test.server", 367 grpc.WithTransportCredentials(insecure.NewCredentials()), 368 grpc.WithResolvers(r), 369 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 370 if err != nil { 371 t.Fatalf("failed to dial: %v", err) 372 } 373 defer cc.Close() 374 testc := testpb.NewTestServiceClient(cc) 375 // The first RPC should fail because there's no address. 376 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 377 defer cancel() 378 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded { 379 t.Fatalf("EmptyCall() = _, %v, want _, DeadlineExceeded", err) 380 } 381 382 var resolvedAddrs []resolver.Address 383 for i := 0; i < backendCount; i++ { 384 resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: test.addresses[i]}) 385 } 386 387 r.UpdateState(resolver.State{Addresses: resolvedAddrs}) 388 var p peer.Peer 389 // Make sure connections to all servers are up. 390 for si := 0; si < backendCount; si++ { 391 var connected bool 392 for i := 0; i < 1000; i++ { 393 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 394 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 395 } 396 if p.Addr.String() == test.addresses[si] { 397 connected = true 398 break 399 } 400 time.Sleep(time.Millisecond) 401 } 402 if !connected { 403 t.Fatalf("Connection to %v was not up after more than 1 second", test.addresses[si]) 404 } 405 } 406 407 for i := 0; i < 3*backendCount; i++ { 408 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 409 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 410 } 411 if p.Addr.String() != test.addresses[i%backendCount] { 412 t.Fatalf("Index %d: want peer %v, got peer %v", i, test.addresses[i%backendCount], p.Addr.String()) 413 } 414 } 415 416 // Stop one server, RPCs should roundrobin among the remaining servers. 417 backendCount-- 418 test.servers[backendCount].Stop() 419 // Loop until see server[backendCount-1] twice without seeing server[backendCount]. 420 var targetSeen int 421 for i := 0; i < 1000; i++ { 422 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 423 targetSeen = 0 424 t.Logf("EmptyCall() = _, %v, want _, <nil>", err) 425 // Due to a race, this RPC could possibly get the connection that 426 // was closing, and this RPC may fail. Keep trying when this 427 // happens. 428 continue 429 } 430 switch p.Addr.String() { 431 case test.addresses[backendCount-1]: 432 targetSeen++ 433 case test.addresses[backendCount]: 434 // Reset targetSeen if peer is server[backendCount]. 435 targetSeen = 0 436 } 437 // Break to make sure the last picked address is server[-1], so the following for loop won't be flaky. 438 if targetSeen >= 2 { 439 break 440 } 441 } 442 if targetSeen != 2 { 443 t.Fatal("Failed to see server[backendCount-1] twice without seeing server[backendCount]") 444 } 445 for i := 0; i < 3*backendCount; i++ { 446 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 447 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 448 } 449 if p.Addr.String() != test.addresses[i%backendCount] { 450 t.Errorf("Index %d: want peer %v, got peer %v", i, test.addresses[i%backendCount], p.Addr.String()) 451 } 452 } 453 } 454 455 func (s) TestAllServersDown(t *testing.T) { 456 r := manual.NewBuilderWithScheme("whatever") 457 458 backendCount := 3 459 test, err := startTestServers(backendCount, false) 460 if err != nil { 461 t.Fatalf("failed to start servers: %v", err) 462 } 463 defer test.cleanup() 464 465 cc, err := grpc.Dial(r.Scheme()+":///test.server", 466 grpc.WithTransportCredentials(insecure.NewCredentials()), 467 grpc.WithResolvers(r), 468 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 469 if err != nil { 470 t.Fatalf("failed to dial: %v", err) 471 } 472 defer cc.Close() 473 testc := testpb.NewTestServiceClient(cc) 474 // The first RPC should fail because there's no address. 475 ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) 476 defer cancel() 477 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded { 478 t.Fatalf("EmptyCall() = _, %v, want _, DeadlineExceeded", err) 479 } 480 481 var resolvedAddrs []resolver.Address 482 for i := 0; i < backendCount; i++ { 483 resolvedAddrs = append(resolvedAddrs, resolver.Address{Addr: test.addresses[i]}) 484 } 485 486 r.UpdateState(resolver.State{Addresses: resolvedAddrs}) 487 var p peer.Peer 488 // Make sure connections to all servers are up. 489 for si := 0; si < backendCount; si++ { 490 var connected bool 491 for i := 0; i < 1000; i++ { 492 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 493 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 494 } 495 if p.Addr.String() == test.addresses[si] { 496 connected = true 497 break 498 } 499 time.Sleep(time.Millisecond) 500 } 501 if !connected { 502 t.Fatalf("Connection to %v was not up after more than 1 second", test.addresses[si]) 503 } 504 } 505 506 for i := 0; i < 3*backendCount; i++ { 507 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}, grpc.Peer(&p)); err != nil { 508 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 509 } 510 if p.Addr.String() != test.addresses[i%backendCount] { 511 t.Fatalf("Index %d: want peer %v, got peer %v", i, test.addresses[i%backendCount], p.Addr.String()) 512 } 513 } 514 515 // All servers are stopped, failfast RPC should fail with unavailable. 516 for i := 0; i < backendCount; i++ { 517 test.servers[i].Stop() 518 } 519 time.Sleep(100 * time.Millisecond) 520 for i := 0; i < 1000; i++ { 521 if _, err := testc.EmptyCall(context.Background(), &testpb.Empty{}); status.Code(err) == codes.Unavailable { 522 return 523 } 524 time.Sleep(time.Millisecond) 525 } 526 t.Fatalf("Failfast RPCs didn't fail with Unavailable after all servers are stopped") 527 } 528 529 func (s) TestUpdateAddressAttributes(t *testing.T) { 530 r := manual.NewBuilderWithScheme("whatever") 531 532 test, err := startTestServers(1, true) 533 if err != nil { 534 t.Fatalf("failed to start servers: %v", err) 535 } 536 defer test.cleanup() 537 538 cc, err := grpc.Dial(r.Scheme()+":///test.server", 539 grpc.WithTransportCredentials(insecure.NewCredentials()), 540 grpc.WithResolvers(r), 541 grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingConfig": [{"%s":{}}]}`, roundrobin.Name))) 542 if err != nil { 543 t.Fatalf("failed to dial: %v", err) 544 } 545 defer cc.Close() 546 testc := testpb.NewTestServiceClient(cc) 547 548 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 549 defer cancel() 550 551 // The first RPC should fail because there's no address. 552 ctxShort, cancel2 := context.WithTimeout(ctx, time.Millisecond) 553 defer cancel2() 554 if _, err := testc.EmptyCall(ctxShort, &testpb.Empty{}); status.Code(err) != codes.DeadlineExceeded { 555 t.Fatalf("EmptyCall() = _, %v, want _, DeadlineExceeded", err) 556 } 557 558 r.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: test.addresses[0]}}}) 559 // The second RPC should succeed. 560 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); err != nil { 561 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 562 } 563 // The second RPC should not set metadata, so there's no md in the channel. 564 md1 := <-test.serverImpls[0].testMDChan 565 if md1 != nil { 566 t.Fatalf("got md: %v, want empty metadata", md1) 567 } 568 569 const testMDValue = "test-md-value" 570 // Update metadata in address. 571 r.UpdateState(resolver.State{Addresses: []resolver.Address{ 572 imetadata.Set(resolver.Address{Addr: test.addresses[0]}, metadata.Pairs(testMDKey, testMDValue)), 573 }}) 574 575 // A future RPC should send metadata with it. The update doesn't 576 // necessarily happen synchronously, so we wait some time before failing if 577 // some RPCs do not contain it. 578 for { 579 if _, err := testc.EmptyCall(ctx, &testpb.Empty{}); err != nil { 580 if status.Code(err) == codes.DeadlineExceeded { 581 t.Fatalf("timed out waiting for metadata in response") 582 } 583 t.Fatalf("EmptyCall() = _, %v, want _, <nil>", err) 584 } 585 md2 := <-test.serverImpls[0].testMDChan 586 if len(md2) == 1 && md2[0] == testMDValue { 587 return 588 } 589 time.Sleep(10 * time.Millisecond) 590 } 591 }