github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/coordination/session.go (about) 1 package coordination 2 3 import ( 4 "context" 5 "encoding/binary" 6 "math" 7 "math/rand" 8 "sync" 9 "time" 10 11 "github.com/ydb-platform/ydb-go-genproto/Ydb_Coordination_V1" 12 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb" 13 "github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Coordination" 14 15 "github.com/ydb-platform/ydb-go-sdk/v3/coordination" 16 "github.com/ydb-platform/ydb-go-sdk/v3/coordination/options" 17 "github.com/ydb-platform/ydb-go-sdk/v3/internal/coordination/conversation" 18 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext" 19 "github.com/ydb-platform/ydb-go-sdk/v3/trace" 20 ) 21 22 type session struct { 23 options *options.CreateSessionOptions 24 client *Client 25 26 ctx context.Context //nolint:containedctx 27 cancel context.CancelFunc 28 sessionClosedChan chan struct{} 29 controller *conversation.Controller 30 sessionID uint64 31 32 mutex sync.Mutex // guards the field below 33 lastGoodResponseTime time.Time 34 cancelStream context.CancelFunc 35 } 36 37 type lease struct { 38 session *session 39 name string 40 ctx context.Context //nolint:containedctx 41 cancel context.CancelFunc 42 } 43 44 func createSession( 45 ctx context.Context, 46 client *Client, 47 path string, 48 opts *options.CreateSessionOptions, 49 ) (*session, error) { 50 sessionCtx, cancel := xcontext.WithCancel(xcontext.ValueOnly(ctx)) 51 s := session{ 52 options: opts, 53 client: client, 54 ctx: sessionCtx, 55 cancel: cancel, 56 sessionClosedChan: make(chan struct{}), 57 controller: conversation.NewController(), 58 } 59 client.sessionCreated(&s) 60 61 sessionStartedChan := make(chan struct{}) 62 go s.mainLoop(path, sessionStartedChan) 63 64 select { 65 case <-ctx.Done(): 66 cancel() 67 68 return nil, ctx.Err() 69 case <-sessionStartedChan: 70 } 71 72 return &s, nil 73 } 74 75 func newProtectionKey() []byte { 76 key := make([]byte, 8) //nolint:gomnd 77 binary.LittleEndian.PutUint64(key, rand.Uint64()) //nolint:gosec 78 79 return key 80 } 81 82 func newReqID() uint64 { 83 return rand.Uint64() //nolint:gosec 84 } 85 86 func (s *session) updateLastGoodResponseTime() { 87 s.mutex.Lock() 88 defer s.mutex.Unlock() 89 90 now := time.Now() 91 92 if now.After(s.lastGoodResponseTime) { 93 s.lastGoodResponseTime = now 94 } 95 } 96 97 func (s *session) getLastGoodResponseTime() time.Time { 98 s.mutex.Lock() 99 defer s.mutex.Unlock() 100 101 return s.lastGoodResponseTime 102 } 103 104 func (s *session) updateCancelStream(cancel context.CancelFunc) { 105 s.mutex.Lock() 106 defer s.mutex.Unlock() 107 108 s.cancelStream = cancel 109 } 110 111 // Create a new gRPC stream using an independent context. 112 // 113 //nolint:funlen 114 func (s *session) newStream( 115 streamCtx context.Context, 116 cancelStream context.CancelFunc, 117 ) (Ydb_Coordination_V1.CoordinationService_SessionClient, error) { 118 // This deadline if final. If we have not got a session before it, the session is either expired or has never been 119 // created. 120 var deadline time.Time 121 if s.sessionID != 0 { 122 deadline = s.getLastGoodResponseTime().Add(s.options.SessionTimeout) 123 } else { 124 // Large enough to make the loop infinite, small enough to allow the maximum duration value (~290 years). 125 deadline = time.Now().Add(time.Hour * 24 * 365 * 100) //nolint:gomnd 126 } 127 128 lastChance := false 129 for { 130 result := make(chan Ydb_Coordination_V1.CoordinationService_SessionClient, 1) 131 go func() { 132 var err error 133 onDone := trace.CoordinationOnStreamNew(s.client.config.Trace()) 134 defer func() { 135 onDone(err) 136 }() 137 138 client, err := s.client.client.Session(streamCtx) 139 result <- client 140 }() 141 142 var client Ydb_Coordination_V1.CoordinationService_SessionClient 143 if lastChance { 144 timer := time.NewTimer(s.options.SessionKeepAliveTimeout) 145 select { 146 case <-timer.C: 147 case client = <-result: 148 } 149 timer.Stop() 150 151 if client != nil { 152 return client, nil 153 } 154 155 cancelStream() 156 157 return nil, s.ctx.Err() 158 } 159 160 // Since the deadline is probably large enough, avoid the timer leak with time.After. 161 timer := time.NewTimer(time.Until(deadline)) 162 select { 163 case <-s.ctx.Done(): 164 case client = <-result: 165 case <-timer.C: 166 trace.CoordinationOnSessionClientTimeout( 167 s.client.config.Trace(), 168 s.getLastGoodResponseTime(), 169 s.options.SessionTimeout, 170 ) 171 cancelStream() 172 173 return nil, coordination.ErrSessionClosed 174 } 175 timer.Stop() 176 177 if client != nil { 178 return client, nil 179 } 180 181 // Waiting for some time before trying to reconnect. 182 sessionReconnectDelay := time.NewTimer(s.options.SessionReconnectDelay) 183 select { 184 case <-sessionReconnectDelay.C: 185 case <-s.ctx.Done(): 186 } 187 sessionReconnectDelay.Stop() 188 189 if s.ctx.Err() != nil { 190 // Give this session the last chance to stop gracefully if the session is canceled in the reconnect cycle. 191 if s.sessionID != 0 { 192 lastChance = true 193 } else { 194 cancelStream() 195 196 return nil, s.ctx.Err() 197 } 198 } 199 } 200 } 201 202 //nolint:funlen 203 func (s *session) mainLoop(path string, sessionStartedChan chan struct{}) { 204 defer s.client.sessionClosed(s) 205 defer close(s.sessionClosedChan) 206 defer s.cancel() 207 208 var seqNo uint64 209 210 protectionKey := newProtectionKey() 211 closing := false 212 213 for { 214 // Create a new grpc stream and start the receiver and sender loops. 215 // 216 // We use the stream context as a way to inform the main loop that the session must be reconnected if an 217 // unrecoverable error occurs in the receiver or sender loop. This also helps stop the other loop if an error 218 // is caught on only one of them. 219 // 220 // We intentionally place a stream context outside the scope of any existing contexts to make an attempt to 221 // close the session gracefully at the end of the main loop. 222 223 streamCtx, cancelStream := context.WithCancel(context.Background()) 224 sessionClient, err := s.newStream(streamCtx, cancelStream) 225 if err != nil { 226 // Giving up, we can do nothing without a stream. 227 s.controller.Close(nil) 228 229 return 230 } 231 232 s.updateCancelStream(cancelStream) 233 234 // Start the loops. 235 wg := sync.WaitGroup{} 236 wg.Add(2) //nolint:gomnd 237 sessionStarted := make(chan *Ydb_Coordination.SessionResponse_SessionStarted, 1) 238 sessionStopped := make(chan *Ydb_Coordination.SessionResponse_SessionStopped, 1) 239 startSending := make(chan struct{}) 240 s.controller.OnAttach() 241 242 go s.receiveLoop(&wg, sessionClient, cancelStream, sessionStarted, sessionStopped) 243 go s.sendLoop( 244 &wg, 245 sessionClient, 246 streamCtx, 247 cancelStream, 248 startSending, 249 path, 250 protectionKey, 251 s.sessionID, 252 seqNo, 253 ) 254 255 // Wait for the session started response unless the stream context is done. We intentionally do not take into 256 // account stream context cancellation in order to proceed with the graceful shutdown if it requires reconnect. 257 sessionStartTimer := time.NewTimer(s.options.SessionStartTimeout) 258 select { 259 case start := <-sessionStarted: 260 trace.CoordinationOnSessionStarted(s.client.config.Trace(), start.GetSessionId(), s.sessionID) 261 if s.sessionID == 0 { 262 s.sessionID = start.GetSessionId() 263 close(sessionStartedChan) 264 } else if start.GetSessionId() != s.sessionID { 265 // Reconnect if the server response is invalid. 266 cancelStream() 267 } 268 close(startSending) 269 case <-sessionStartTimer.C: 270 // Reconnect if no response was received before the timeout occurred. 271 trace.CoordinationOnSessionStartTimeout(s.client.config.Trace(), s.options.SessionStartTimeout) 272 cancelStream() 273 case <-streamCtx.Done(): 274 case <-s.ctx.Done(): 275 } 276 sessionStartTimer.Stop() 277 278 for { 279 // Respect the failure reason priority: if the session context is done, we must stop the session, even 280 // though the stream context may also be canceled. 281 if s.ctx.Err() != nil { 282 closing = true 283 284 break 285 } 286 if streamCtx.Err() != nil { 287 // Reconnect if an error occurred during the start session conversation. 288 break 289 } 290 291 keepAliveTime := time.Until(s.getLastGoodResponseTime().Add(s.options.SessionKeepAliveTimeout)) 292 keepAliveTimeTimer := time.NewTimer(keepAliveTime) 293 select { 294 case <-keepAliveTimeTimer.C: 295 last := s.getLastGoodResponseTime() 296 if time.Since(last) > s.options.SessionKeepAliveTimeout { 297 // Reconnect if the underlying stream is likely to be dead. 298 trace.CoordinationOnSessionKeepAliveTimeout( 299 s.client.config.Trace(), 300 last, 301 s.options.SessionKeepAliveTimeout, 302 ) 303 cancelStream() 304 } 305 case <-streamCtx.Done(): 306 case <-s.ctx.Done(): 307 } 308 keepAliveTimeTimer.Stop() 309 } 310 311 if closing { 312 // No need to stop the session if it was not started. 313 if s.sessionID == 0 { 314 s.controller.Close(nil) 315 cancelStream() 316 317 return 318 } 319 320 trace.CoordinationOnSessionStop(s.client.config.Trace(), s.sessionID) 321 s.controller.Close(conversation.NewConversation( 322 func() *Ydb_Coordination.SessionRequest { 323 return &Ydb_Coordination.SessionRequest{ 324 Request: &Ydb_Coordination.SessionRequest_SessionStop_{ 325 SessionStop: &Ydb_Coordination.SessionRequest_SessionStop{}, 326 }, 327 } 328 }), 329 ) 330 331 // Wait for the session stopped response unless the stream context is done. 332 sessionStopTimeout := time.NewTimer(s.options.SessionStopTimeout) 333 select { 334 case stop := <-sessionStopped: 335 sessionStopTimeout.Stop() 336 trace.CoordinationOnSessionStopped(s.client.config.Trace(), stop.GetSessionId(), s.sessionID) 337 if stop.GetSessionId() == s.sessionID { 338 cancelStream() 339 340 return 341 } 342 343 // Reconnect if the server response is invalid. 344 cancelStream() 345 case <-sessionStopTimeout.C: 346 sessionStopTimeout.Stop() // no really need, call stop for common style only 347 348 // Reconnect if no response was received before the timeout occurred. 349 trace.CoordinationOnSessionStopTimeout(s.client.config.Trace(), s.options.SessionStopTimeout) 350 cancelStream() 351 case <-s.ctx.Done(): 352 sessionStopTimeout.Stop() 353 cancelStream() 354 355 return 356 case <-streamCtx.Done(): 357 sessionStopTimeout.Stop() 358 } 359 } 360 361 // Make sure no one is processing the stream anymore. 362 wg.Wait() 363 364 s.controller.OnDetach() 365 seqNo++ 366 } 367 } 368 369 //nolint:funlen 370 func (s *session) receiveLoop( 371 wg *sync.WaitGroup, 372 sessionClient Ydb_Coordination_V1.CoordinationService_SessionClient, 373 cancelStream context.CancelFunc, 374 sessionStarted chan *Ydb_Coordination.SessionResponse_SessionStarted, 375 sessionStopped chan *Ydb_Coordination.SessionResponse_SessionStopped, 376 ) { 377 // If the sendLoop is done, make sure the stream is also canceled to make the receiveLoop finish its work and cause 378 // reconnect. 379 defer wg.Done() 380 defer cancelStream() 381 382 for { 383 onDone := trace.CoordinationOnSessionReceive(s.client.config.Trace()) 384 message, err := sessionClient.Recv() 385 if err != nil { 386 // Any stream error is unrecoverable, try to reconnect. 387 onDone(nil, err) 388 389 return 390 } 391 onDone(message, nil) 392 393 switch message.GetResponse().(type) { 394 case *Ydb_Coordination.SessionResponse_Failure_: 395 if message.GetFailure().GetStatus() == Ydb.StatusIds_SESSION_EXPIRED || 396 message.GetFailure().GetStatus() == Ydb.StatusIds_UNAUTHORIZED || 397 message.GetFailure().GetStatus() == Ydb.StatusIds_NOT_FOUND { 398 // Consider the session expired if we got an unrecoverable status. 399 trace.CoordinationOnSessionServerExpire(s.client.config.Trace(), message.GetFailure()) 400 401 return 402 } 403 404 trace.CoordinationOnSessionServerError(s.client.config.Trace(), message.GetFailure()) 405 406 return 407 case *Ydb_Coordination.SessionResponse_SessionStarted_: 408 sessionStarted <- message.GetSessionStarted() 409 s.updateLastGoodResponseTime() 410 case *Ydb_Coordination.SessionResponse_SessionStopped_: 411 sessionStopped <- message.GetSessionStopped() 412 s.cancel() 413 414 return 415 case *Ydb_Coordination.SessionResponse_Ping: 416 opaque := message.GetPing().GetOpaque() 417 err := s.controller.PushFront(conversation.NewConversation( 418 func() *Ydb_Coordination.SessionRequest { 419 return &Ydb_Coordination.SessionRequest{ 420 Request: &Ydb_Coordination.SessionRequest_Pong{ 421 Pong: &Ydb_Coordination.SessionRequest_PingPong{ 422 Opaque: opaque, 423 }, 424 }, 425 } 426 }), 427 ) 428 if err != nil { 429 // The session is closed if we cannot send the pong request back, so just exit the loop. 430 return 431 } 432 s.updateLastGoodResponseTime() 433 case *Ydb_Coordination.SessionResponse_Pong: 434 // Ignore pongs since we do not ping the server. 435 default: 436 if !s.controller.OnRecv(message) { 437 // Reconnect if the message is not from any known conversation. 438 trace.CoordinationOnSessionReceiveUnexpected(s.client.config.Trace(), message) 439 440 return 441 } 442 443 s.updateLastGoodResponseTime() 444 } 445 } 446 } 447 448 //nolint:revive,funlen 449 func (s *session) sendLoop( 450 wg *sync.WaitGroup, 451 sessionClient Ydb_Coordination_V1.CoordinationService_SessionClient, 452 streamCtx context.Context, 453 cancelStream context.CancelFunc, 454 startSending chan struct{}, 455 path string, 456 protectionKey []byte, 457 sessionID uint64, 458 seqNo uint64, 459 ) { 460 // If the sendLoop is done, make sure the stream is also canceled to make the receiveLoop finish its work and cause 461 // reconnect. 462 defer wg.Done() 463 defer cancelStream() 464 465 // Start a new session. 466 onDone := trace.CoordinationOnSessionStart(s.client.config.Trace()) 467 startSession := Ydb_Coordination.SessionRequest{ 468 Request: &Ydb_Coordination.SessionRequest_SessionStart_{ 469 SessionStart: &Ydb_Coordination.SessionRequest_SessionStart{ 470 Path: path, 471 SessionId: sessionID, 472 TimeoutMillis: uint64(s.options.SessionTimeout.Milliseconds()), 473 ProtectionKey: protectionKey, 474 SeqNo: seqNo, 475 Description: s.options.Description, 476 }, 477 }, 478 } 479 err := sessionClient.Send(&startSession) 480 if err != nil { 481 // Reconnect if a session cannot be started in this stream. 482 onDone(err) 483 484 return 485 } 486 onDone(nil) 487 488 // Wait for a response to the session start request in order to carry over the accumulated conversations until the 489 // server confirms that the session is running. This is not absolutely necessary but helps the client to not fail 490 // non-idempotent requests in case of the session handshake errors. 491 select { 492 case <-streamCtx.Done(): 493 case <-startSending: 494 } 495 496 for { 497 message, err := s.controller.OnSend(streamCtx) 498 if err != nil { 499 return 500 } 501 502 onSendDone := trace.CoordinationOnSessionSend(s.client.config.Trace(), message) 503 err = sessionClient.Send(message) 504 if err != nil { 505 // Any stream error is unrecoverable, try to reconnect. 506 onSendDone(err) 507 508 return 509 } 510 onSendDone(nil) 511 } 512 } 513 514 func (s *session) Context() context.Context { 515 return s.ctx 516 } 517 518 func (s *session) Close(ctx context.Context) error { 519 s.cancel() 520 521 select { 522 case <-s.sessionClosedChan: 523 case <-ctx.Done(): 524 return ctx.Err() 525 } 526 527 return nil 528 } 529 530 func (s *session) Reconnect() { 531 s.mutex.Lock() 532 defer s.mutex.Unlock() 533 534 if s.cancelStream != nil { 535 s.cancelStream() 536 } 537 } 538 539 func (s *session) SessionID() uint64 { 540 return s.sessionID 541 } 542 543 func (s *session) CreateSemaphore( 544 ctx context.Context, 545 name string, 546 limit uint64, 547 opts ...options.CreateSemaphoreOption, 548 ) error { 549 req := conversation.NewConversation( 550 func() *Ydb_Coordination.SessionRequest { 551 createSemaphore := Ydb_Coordination.SessionRequest_CreateSemaphore{ 552 ReqId: newReqID(), 553 Name: name, 554 Limit: limit, 555 } 556 for _, o := range opts { 557 if o != nil { 558 o(&createSemaphore) 559 } 560 } 561 562 return &Ydb_Coordination.SessionRequest{ 563 Request: &Ydb_Coordination.SessionRequest_CreateSemaphore_{ 564 CreateSemaphore: &createSemaphore, 565 }, 566 } 567 }, 568 conversation.WithResponseFilter(func( 569 request *Ydb_Coordination.SessionRequest, 570 response *Ydb_Coordination.SessionResponse, 571 ) bool { 572 return response.GetCreateSemaphoreResult().GetReqId() == request.GetCreateSemaphore().GetReqId() 573 }), 574 ) 575 if err := s.controller.PushBack(req); err != nil { 576 return err 577 } 578 579 _, err := s.controller.Await(ctx, req) 580 if err != nil { 581 return err 582 } 583 584 return nil 585 } 586 587 func (s *session) UpdateSemaphore( 588 ctx context.Context, 589 name string, 590 opts ...options.UpdateSemaphoreOption, 591 ) error { 592 req := conversation.NewConversation( 593 func() *Ydb_Coordination.SessionRequest { 594 updateSemaphore := Ydb_Coordination.SessionRequest_UpdateSemaphore{ 595 ReqId: newReqID(), 596 Name: name, 597 } 598 for _, o := range opts { 599 if o != nil { 600 o(&updateSemaphore) 601 } 602 } 603 604 return &Ydb_Coordination.SessionRequest{ 605 Request: &Ydb_Coordination.SessionRequest_UpdateSemaphore_{ 606 UpdateSemaphore: &updateSemaphore, 607 }, 608 } 609 }, 610 conversation.WithResponseFilter(func( 611 request *Ydb_Coordination.SessionRequest, 612 response *Ydb_Coordination.SessionResponse, 613 ) bool { 614 return response.GetUpdateSemaphoreResult().GetReqId() == request.GetUpdateSemaphore().GetReqId() 615 }), 616 conversation.WithConflictKey(name), 617 conversation.WithIdempotence(true), 618 ) 619 if err := s.controller.PushBack(req); err != nil { 620 return err 621 } 622 623 _, err := s.controller.Await(ctx, req) 624 if err != nil { 625 return err 626 } 627 628 return nil 629 } 630 631 func (s *session) DeleteSemaphore( 632 ctx context.Context, 633 name string, 634 opts ...options.DeleteSemaphoreOption, 635 ) error { 636 req := conversation.NewConversation( 637 func() *Ydb_Coordination.SessionRequest { 638 deleteSemaphore := Ydb_Coordination.SessionRequest_DeleteSemaphore{ 639 ReqId: newReqID(), 640 Name: name, 641 } 642 for _, o := range opts { 643 if o != nil { 644 o(&deleteSemaphore) 645 } 646 } 647 648 return &Ydb_Coordination.SessionRequest{ 649 Request: &Ydb_Coordination.SessionRequest_DeleteSemaphore_{ 650 DeleteSemaphore: &deleteSemaphore, 651 }, 652 } 653 }, 654 conversation.WithResponseFilter(func( 655 request *Ydb_Coordination.SessionRequest, 656 response *Ydb_Coordination.SessionResponse, 657 ) bool { 658 return response.GetDeleteSemaphoreResult().GetReqId() == request.GetDeleteSemaphore().GetReqId() 659 }), 660 conversation.WithConflictKey(name), 661 ) 662 if err := s.controller.PushBack(req); err != nil { 663 return err 664 } 665 666 _, err := s.controller.Await(ctx, req) 667 if err != nil { 668 return err 669 } 670 671 return nil 672 } 673 674 func (s *session) DescribeSemaphore( 675 ctx context.Context, 676 name string, 677 opts ...options.DescribeSemaphoreOption, 678 ) (*coordination.SemaphoreDescription, error) { 679 req := conversation.NewConversation( 680 func() *Ydb_Coordination.SessionRequest { 681 describeSemaphore := Ydb_Coordination.SessionRequest_DescribeSemaphore{ 682 ReqId: newReqID(), 683 Name: name, 684 } 685 for _, o := range opts { 686 if o != nil { 687 o(&describeSemaphore) 688 } 689 } 690 691 return &Ydb_Coordination.SessionRequest{ 692 Request: &Ydb_Coordination.SessionRequest_DescribeSemaphore_{ 693 DescribeSemaphore: &describeSemaphore, 694 }, 695 } 696 }, 697 conversation.WithResponseFilter(func( 698 request *Ydb_Coordination.SessionRequest, 699 response *Ydb_Coordination.SessionResponse, 700 ) bool { 701 return response.GetDescribeSemaphoreResult().GetReqId() == request.GetDescribeSemaphore().GetReqId() 702 }), 703 conversation.WithConflictKey(name), 704 conversation.WithIdempotence(true), 705 ) 706 if err := s.controller.PushBack(req); err != nil { 707 return nil, err 708 } 709 710 resp, err := s.controller.Await(ctx, req) 711 if err != nil { 712 return nil, err 713 } 714 715 return convertSemaphoreDescription(resp.GetDescribeSemaphoreResult().GetSemaphoreDescription()), nil 716 } 717 718 func convertSemaphoreDescription( 719 desc *Ydb_Coordination.SemaphoreDescription, 720 ) *coordination.SemaphoreDescription { 721 var result coordination.SemaphoreDescription 722 723 if desc != nil { 724 result.Name = desc.GetName() 725 result.Limit = desc.GetLimit() 726 result.Ephemeral = desc.GetEphemeral() 727 result.Count = desc.GetCount() 728 result.Data = desc.GetData() 729 result.Owners = convertSemaphoreSessions(desc.GetOwners()) 730 result.Waiters = convertSemaphoreSessions(desc.GetWaiters()) 731 } 732 733 return &result 734 } 735 736 func convertSemaphoreSessions( 737 sessions []*Ydb_Coordination.SemaphoreSession, 738 ) []*coordination.SemaphoreSession { 739 if sessions == nil { 740 return nil 741 } 742 743 result := make([]*coordination.SemaphoreSession, len(sessions)) 744 for i, s := range sessions { 745 result[i] = convertSemaphoreSession(s) 746 } 747 748 return result 749 } 750 751 func convertSemaphoreSession( 752 session *Ydb_Coordination.SemaphoreSession, 753 ) *coordination.SemaphoreSession { 754 var result coordination.SemaphoreSession 755 756 if session != nil { 757 result.SessionID = session.GetSessionId() 758 result.Count = session.GetCount() 759 result.OrderID = session.GetOrderId() 760 result.Data = session.GetData() 761 if session.GetTimeoutMillis() == math.MaxUint64 { 762 result.Timeout = time.Duration(math.MaxInt64) 763 } else { 764 // The service does not allow big timeout values, so the conversion seems to be safe. 765 result.Timeout = time.Duration(session.GetTimeoutMillis()) * time.Millisecond 766 } 767 } 768 769 return &result 770 } 771 772 //nolint:funlen 773 func (s *session) AcquireSemaphore( 774 ctx context.Context, 775 name string, 776 count uint64, 777 opts ...options.AcquireSemaphoreOption, 778 ) (coordination.Lease, error) { 779 req := conversation.NewConversation( 780 func() *Ydb_Coordination.SessionRequest { 781 acquireSemaphore := Ydb_Coordination.SessionRequest_AcquireSemaphore{ 782 ReqId: newReqID(), 783 Name: name, 784 Count: count, 785 TimeoutMillis: math.MaxUint64, 786 } 787 for _, o := range opts { 788 if o != nil { 789 o(&acquireSemaphore) 790 } 791 } 792 793 return &Ydb_Coordination.SessionRequest{ 794 Request: &Ydb_Coordination.SessionRequest_AcquireSemaphore_{ 795 AcquireSemaphore: &acquireSemaphore, 796 }, 797 } 798 }, 799 conversation.WithResponseFilter(func( 800 request *Ydb_Coordination.SessionRequest, 801 response *Ydb_Coordination.SessionResponse, 802 ) bool { 803 return response.GetAcquireSemaphoreResult().GetReqId() == request.GetAcquireSemaphore().GetReqId() 804 }), 805 conversation.WithAcknowledgeFilter(func( 806 request *Ydb_Coordination.SessionRequest, 807 response *Ydb_Coordination.SessionResponse, 808 ) bool { 809 return response.GetAcquireSemaphorePending().GetReqId() == request.GetAcquireSemaphore().GetReqId() 810 }), 811 conversation.WithCancelMessage( 812 func(request *Ydb_Coordination.SessionRequest) *Ydb_Coordination.SessionRequest { 813 return &Ydb_Coordination.SessionRequest{ 814 Request: &Ydb_Coordination.SessionRequest_ReleaseSemaphore_{ 815 ReleaseSemaphore: &Ydb_Coordination.SessionRequest_ReleaseSemaphore{ 816 Name: name, 817 ReqId: newReqID(), 818 }, 819 }, 820 } 821 }, 822 func( 823 request *Ydb_Coordination.SessionRequest, 824 response *Ydb_Coordination.SessionResponse, 825 ) bool { 826 return response.GetReleaseSemaphoreResult().GetReqId() == request.GetReleaseSemaphore().GetReqId() 827 }, 828 ), 829 conversation.WithConflictKey(name), 830 conversation.WithIdempotence(true), 831 ) 832 if err := s.controller.PushBack(req); err != nil { 833 return nil, err 834 } 835 836 resp, err := s.controller.Await(ctx, req) 837 if err != nil { 838 return nil, err 839 } 840 841 if !resp.GetAcquireSemaphoreResult().GetAcquired() { 842 return nil, coordination.ErrAcquireTimeout 843 } 844 845 ctx, cancel := context.WithCancel(s.ctx) 846 847 return &lease{ 848 session: s, 849 name: name, 850 ctx: ctx, 851 cancel: cancel, 852 }, nil 853 } 854 855 func (l *lease) Context() context.Context { 856 return l.ctx 857 } 858 859 func (l *lease) Release() error { 860 req := conversation.NewConversation( 861 func() *Ydb_Coordination.SessionRequest { 862 return &Ydb_Coordination.SessionRequest{ 863 Request: &Ydb_Coordination.SessionRequest_ReleaseSemaphore_{ 864 ReleaseSemaphore: &Ydb_Coordination.SessionRequest_ReleaseSemaphore{ 865 ReqId: newReqID(), 866 Name: l.name, 867 }, 868 }, 869 } 870 }, 871 conversation.WithResponseFilter(func( 872 request *Ydb_Coordination.SessionRequest, 873 response *Ydb_Coordination.SessionResponse, 874 ) bool { 875 return response.GetReleaseSemaphoreResult().GetReqId() == request.GetReleaseSemaphore().GetReqId() 876 }), 877 conversation.WithConflictKey(l.name), 878 conversation.WithIdempotence(true), 879 ) 880 if err := l.session.controller.PushBack(req); err != nil { 881 return err 882 } 883 884 _, err := l.session.controller.Await(l.session.ctx, req) 885 if err != nil { 886 return err 887 } 888 889 l.cancel() 890 891 return nil 892 } 893 894 func (l *lease) Session() coordination.Session { 895 return l.session 896 }