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  }