github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/pool/pool.go (about)

     1  package pool
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/jonboulle/clockwork"
    10  
    11  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xlist"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/retry"
    17  )
    18  
    19  type (
    20  	Item interface {
    21  		IsAlive() bool
    22  		Close(ctx context.Context) error
    23  	}
    24  	ItemConstraint[T any] interface {
    25  		*T
    26  		Item
    27  	}
    28  	Config[PT ItemConstraint[T], T any] struct {
    29  		trace          *Trace
    30  		clock          clockwork.Clock
    31  		limit          int
    32  		createTimeout  time.Duration
    33  		createItem     func(ctx context.Context) (PT, error)
    34  		closeTimeout   time.Duration
    35  		closeItem      func(ctx context.Context, item PT)
    36  		idleTimeToLive time.Duration
    37  		itemUsageLimit uint64
    38  	}
    39  	itemInfo[PT ItemConstraint[T], T any] struct {
    40  		idle       *xlist.Element[PT]
    41  		lastUsage  time.Time
    42  		useCounter *uint64
    43  	}
    44  	waitChPool[PT ItemConstraint[T], T any] interface {
    45  		GetOrNew() *chan PT
    46  		Put(t *chan PT)
    47  	}
    48  	Pool[PT ItemConstraint[T], T any] struct {
    49  		config Config[PT, T]
    50  
    51  		createItem func(ctx context.Context) (PT, error)
    52  		closeItem  func(ctx context.Context, item PT)
    53  
    54  		mu               xsync.RWMutex
    55  		createInProgress int // KIKIMR-9163: in-create-process counter
    56  		index            map[PT]itemInfo[PT, T]
    57  		idle             xlist.List[PT]
    58  		waitQ            xlist.List[*chan PT]
    59  		waitChPool       waitChPool[PT, T]
    60  
    61  		done chan struct{}
    62  	}
    63  	Option[PT ItemConstraint[T], T any] func(c *Config[PT, T])
    64  )
    65  
    66  func WithCreateItemFunc[PT ItemConstraint[T], T any](f func(ctx context.Context) (PT, error)) Option[PT, T] {
    67  	return func(c *Config[PT, T]) {
    68  		c.createItem = f
    69  	}
    70  }
    71  
    72  func WithSyncCloseItem[PT ItemConstraint[T], T any]() Option[PT, T] {
    73  	return func(c *Config[PT, T]) {
    74  		c.closeItem = func(ctx context.Context, item PT) {
    75  			_ = item.Close(ctx)
    76  		}
    77  	}
    78  }
    79  
    80  func WithCreateItemTimeout[PT ItemConstraint[T], T any](t time.Duration) Option[PT, T] {
    81  	return func(c *Config[PT, T]) {
    82  		c.createTimeout = t
    83  	}
    84  }
    85  
    86  func WithCloseItemTimeout[PT ItemConstraint[T], T any](t time.Duration) Option[PT, T] {
    87  	return func(c *Config[PT, T]) {
    88  		c.closeTimeout = t
    89  	}
    90  }
    91  
    92  func WithLimit[PT ItemConstraint[T], T any](size int) Option[PT, T] {
    93  	return func(c *Config[PT, T]) {
    94  		c.limit = size
    95  	}
    96  }
    97  
    98  func WithItemUsageLimit[PT ItemConstraint[T], T any](itemUsageLimit uint64) Option[PT, T] {
    99  	return func(c *Config[PT, T]) {
   100  		c.itemUsageLimit = itemUsageLimit
   101  	}
   102  }
   103  
   104  func WithTrace[PT ItemConstraint[T], T any](t *Trace) Option[PT, T] {
   105  	return func(c *Config[PT, T]) {
   106  		c.trace = t
   107  	}
   108  }
   109  
   110  func WithIdleTimeToLive[PT ItemConstraint[T], T any](idleTTL time.Duration) Option[PT, T] {
   111  	return func(c *Config[PT, T]) {
   112  		c.idleTimeToLive = idleTTL
   113  	}
   114  }
   115  
   116  func WithClock[PT ItemConstraint[T], T any](clock clockwork.Clock) Option[PT, T] {
   117  	return func(c *Config[PT, T]) {
   118  		c.clock = clock
   119  	}
   120  }
   121  
   122  func New[PT ItemConstraint[T], T any](
   123  	ctx context.Context,
   124  	opts ...Option[PT, T],
   125  ) *Pool[PT, T] {
   126  	p := &Pool[PT, T]{
   127  		config: Config[PT, T]{
   128  			trace:         &Trace{},
   129  			clock:         clockwork.NewRealClock(),
   130  			limit:         DefaultLimit,
   131  			createItem:    defaultCreateItem[T, PT],
   132  			createTimeout: defaultCreateTimeout,
   133  			closeTimeout:  defaultCloseTimeout,
   134  		},
   135  		index: make(map[PT]itemInfo[PT, T]),
   136  		idle:  xlist.New[PT](),
   137  		waitQ: xlist.New[*chan PT](),
   138  		waitChPool: &xsync.Pool[chan PT]{
   139  			New: func() *chan PT {
   140  				ch := make(chan PT)
   141  
   142  				return &ch
   143  			},
   144  		},
   145  		done: make(chan struct{}),
   146  	}
   147  
   148  	for _, opt := range opts {
   149  		if opt != nil {
   150  			opt(&p.config)
   151  		}
   152  	}
   153  
   154  	if onNew := p.config.trace.OnNew; onNew != nil {
   155  		onDone := onNew(&ctx,
   156  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.New"),
   157  		)
   158  		if onDone != nil {
   159  			defer func() {
   160  				onDone(p.config.limit)
   161  			}()
   162  		}
   163  	}
   164  
   165  	p.createItem = makeAsyncCreateItemFunc(p)
   166  	if p.config.closeItem != nil {
   167  		p.closeItem = p.config.closeItem
   168  	} else {
   169  		p.closeItem = makeAsyncCloseItemFunc[PT, T](p)
   170  	}
   171  
   172  	return p
   173  }
   174  
   175  // defaultCreateItem returns a new item
   176  func defaultCreateItem[T any, PT ItemConstraint[T]](context.Context) (PT, error) {
   177  	var item T
   178  
   179  	return &item, nil
   180  }
   181  
   182  // makeAsyncCreateItemFunc wraps the createItem function with timeout handling
   183  func makeAsyncCreateItemFunc[PT ItemConstraint[T], T any]( //nolint:funlen
   184  	p *Pool[PT, T],
   185  ) func(ctx context.Context) (PT, error) {
   186  	return func(ctx context.Context) (PT, error) {
   187  		if !xsync.WithLock(&p.mu, func() bool {
   188  			if len(p.index)+p.createInProgress < p.config.limit {
   189  				p.createInProgress++
   190  
   191  				return true
   192  			}
   193  
   194  			return false
   195  		}) {
   196  			return nil, xerrors.WithStackTrace(errPoolIsOverflow)
   197  		}
   198  		defer func() {
   199  			p.mu.WithLock(func() {
   200  				p.createInProgress--
   201  			})
   202  		}()
   203  
   204  		var (
   205  			ch = make(chan struct {
   206  				item PT
   207  				err  error
   208  			})
   209  			done = make(chan struct{})
   210  		)
   211  
   212  		defer close(done)
   213  
   214  		go func() {
   215  			defer close(ch)
   216  
   217  			createCtx, cancelCreate := xcontext.WithDone(xcontext.ValueOnly(ctx), p.done)
   218  			defer cancelCreate()
   219  
   220  			if d := p.config.createTimeout; d > 0 {
   221  				createCtx, cancelCreate = xcontext.WithTimeout(createCtx, d)
   222  				defer cancelCreate()
   223  			}
   224  
   225  			newItem, err := p.config.createItem(createCtx)
   226  			if newItem != nil {
   227  				p.mu.WithLock(func() {
   228  					var useCounter uint64
   229  					p.index[newItem] = itemInfo[PT, T]{
   230  						lastUsage:  p.config.clock.Now(),
   231  						useCounter: &useCounter,
   232  					}
   233  				})
   234  			}
   235  
   236  			select {
   237  			case ch <- struct {
   238  				item PT
   239  				err  error
   240  			}{
   241  				item: newItem,
   242  				err:  xerrors.WithStackTrace(err),
   243  			}:
   244  			case <-done:
   245  				if newItem == nil {
   246  					return
   247  				}
   248  
   249  				_ = p.putItem(createCtx, newItem)
   250  			}
   251  		}()
   252  
   253  		select {
   254  		case <-p.done:
   255  			return nil, xerrors.WithStackTrace(errClosedPool)
   256  		case <-ctx.Done():
   257  			return nil, xerrors.WithStackTrace(ctx.Err())
   258  		case result, has := <-ch:
   259  			if !has {
   260  				return nil, xerrors.WithStackTrace(xerrors.Retryable(errNoProgress))
   261  			}
   262  
   263  			if result.err != nil {
   264  				if xerrors.IsContextError(result.err) {
   265  					return nil, xerrors.WithStackTrace(xerrors.Retryable(result.err))
   266  				}
   267  
   268  				return nil, xerrors.WithStackTrace(result.err)
   269  			}
   270  
   271  			return result.item, nil
   272  		}
   273  	}
   274  }
   275  
   276  func (p *Pool[PT, T]) stats() Stats {
   277  	return Stats{
   278  		Limit:            p.config.limit,
   279  		Index:            len(p.index),
   280  		Idle:             p.idle.Len(),
   281  		Wait:             p.waitQ.Len(),
   282  		CreateInProgress: p.createInProgress,
   283  	}
   284  }
   285  
   286  func (p *Pool[PT, T]) Stats() Stats {
   287  	p.mu.RLock()
   288  	defer p.mu.RUnlock()
   289  
   290  	return p.stats()
   291  }
   292  
   293  func makeAsyncCloseItemFunc[PT ItemConstraint[T], T any](
   294  	p *Pool[PT, T],
   295  ) func(ctx context.Context, item PT) {
   296  	return func(ctx context.Context, item PT) {
   297  		closeItemCtx, closeItemCancel := xcontext.WithDone(xcontext.ValueOnly(ctx), p.done)
   298  		defer closeItemCancel()
   299  
   300  		if d := p.config.closeTimeout; d > 0 {
   301  			closeItemCtx, closeItemCancel = xcontext.WithTimeout(ctx, d)
   302  			defer closeItemCancel()
   303  		}
   304  
   305  		go func() {
   306  			_ = item.Close(closeItemCtx)
   307  		}()
   308  	}
   309  }
   310  
   311  func (p *Pool[PT, T]) changeState(changeState func() Stats) {
   312  	if stats, onChange := changeState(), p.config.trace.OnChange; onChange != nil {
   313  		onChange(stats)
   314  	}
   315  }
   316  
   317  func (p *Pool[PT, T]) try(ctx context.Context, f func(ctx context.Context, item PT) error) (finalErr error) {
   318  	if onTry := p.config.trace.OnTry; onTry != nil {
   319  		onDone := onTry(&ctx,
   320  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).try"),
   321  		)
   322  		if onDone != nil {
   323  			defer func() {
   324  				onDone(finalErr)
   325  			}()
   326  		}
   327  	}
   328  
   329  	select {
   330  	case <-p.done:
   331  		return xerrors.WithStackTrace(errClosedPool)
   332  	case <-ctx.Done():
   333  		return xerrors.WithStackTrace(ctx.Err())
   334  	default:
   335  	}
   336  
   337  	item, err := p.getItem(ctx)
   338  	if err != nil {
   339  		if xerrors.IsYdb(err) {
   340  			return xerrors.WithStackTrace(xerrors.Retryable(err))
   341  		}
   342  
   343  		return xerrors.WithStackTrace(err)
   344  	}
   345  
   346  	defer func() {
   347  		_ = p.putItem(ctx, item)
   348  	}()
   349  
   350  	err = f(ctx, item)
   351  	if err != nil {
   352  		return xerrors.WithStackTrace(err)
   353  	}
   354  
   355  	return nil
   356  }
   357  
   358  func (p *Pool[PT, T]) With(
   359  	ctx context.Context,
   360  	f func(ctx context.Context, item PT) error,
   361  	opts ...retry.Option,
   362  ) (finalErr error) {
   363  	var attempts int
   364  
   365  	if onWith := p.config.trace.OnWith; onWith != nil {
   366  		onDone := onWith(&ctx,
   367  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).With"),
   368  		)
   369  		if onDone != nil {
   370  			defer func() {
   371  				onDone(attempts, finalErr)
   372  			}()
   373  		}
   374  	}
   375  
   376  	err := retry.Retry(ctx, func(ctx context.Context) error {
   377  		attempts++
   378  		err := p.try(ctx, f)
   379  		if err != nil {
   380  			return xerrors.WithStackTrace(err)
   381  		}
   382  
   383  		return nil
   384  	}, opts...)
   385  	if err != nil {
   386  		return xerrors.WithStackTrace(fmt.Errorf("pool.With failed with %d attempts: %w", attempts, err))
   387  	}
   388  
   389  	return nil
   390  }
   391  
   392  func (p *Pool[PT, T]) Close(ctx context.Context) (finalErr error) {
   393  	if onClose := p.config.trace.OnClose; onClose != nil {
   394  		onDone := onClose(&ctx,
   395  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).Close"),
   396  		)
   397  		if onDone != nil {
   398  			defer func() {
   399  				onDone(finalErr)
   400  			}()
   401  		}
   402  	}
   403  
   404  	select {
   405  	case <-p.done:
   406  		return xerrors.WithStackTrace(errClosedPool)
   407  
   408  	default:
   409  		close(p.done)
   410  
   411  		p.mu.Lock()
   412  		defer p.mu.Unlock()
   413  
   414  		p.changeState(func() Stats {
   415  			p.config.limit = 0
   416  
   417  			for el := p.waitQ.Front(); el != nil; el = el.Next() {
   418  				close(*el.Value)
   419  			}
   420  
   421  			p.waitQ.Clear()
   422  
   423  			var wg sync.WaitGroup
   424  			wg.Add(p.idle.Len())
   425  
   426  			for el := p.idle.Front(); el != nil; el = el.Next() {
   427  				go func(item PT) {
   428  					defer wg.Done()
   429  					p.closeItem(ctx, item)
   430  				}(el.Value)
   431  				delete(p.index, el.Value)
   432  			}
   433  
   434  			wg.Wait()
   435  
   436  			p.idle.Clear()
   437  
   438  			return p.stats()
   439  		})
   440  
   441  		return nil
   442  	}
   443  }
   444  
   445  // getWaitCh returns pointer to a channel of sessions.
   446  //
   447  // Note that returning a pointer reduces allocations on sync.Pool usage –
   448  // sync.Client.Get() returns empty interface, which leads to allocation for
   449  // non-pointer values.
   450  func (p *Pool[PT, T]) getWaitCh() *chan PT { //nolint:gocritic
   451  	return p.waitChPool.GetOrNew()
   452  }
   453  
   454  // putWaitCh receives pointer to a channel and makes it available for further
   455  // use.
   456  // Note that ch MUST NOT be owned by any goroutine at the call moment and ch
   457  // MUST NOT contain any value.
   458  func (p *Pool[PT, T]) putWaitCh(ch *chan PT) { //nolint:gocritic
   459  	p.waitChPool.Put(ch)
   460  }
   461  
   462  // p.mu must be held.
   463  func (p *Pool[PT, T]) peekFirstIdle() (item PT, touched time.Time) {
   464  	el := p.idle.Front()
   465  	if el == nil {
   466  		return
   467  	}
   468  	item = el.Value
   469  	info, has := p.index[item]
   470  	if !has || el != info.idle {
   471  		panic(fmt.Sprintf("inconsistent index: (%v, %+v, %+v)", has, el, info.idle))
   472  	}
   473  
   474  	return item, info.lastUsage
   475  }
   476  
   477  // removes first session from idle and resets the keepAliveCount
   478  // to prevent session from dying in the internalPoolGC after it was returned
   479  // to be used only in outgoing functions that make session busy.
   480  // p.mu must be held.
   481  func (p *Pool[PT, T]) removeFirstIdle() PT {
   482  	idle, _ := p.peekFirstIdle()
   483  	if idle != nil {
   484  		info := p.removeIdle(idle)
   485  		p.index[idle] = info
   486  	}
   487  
   488  	return idle
   489  }
   490  
   491  // p.mu must be held.
   492  func (p *Pool[PT, T]) notifyAboutIdle(idle PT) (notified bool) {
   493  	for el := p.waitQ.Front(); el != nil; el = p.waitQ.Front() {
   494  		// Some goroutine is waiting for a session.
   495  		//
   496  		// It could be in this states:
   497  		//   1) Reached the select code and awaiting for a value in channel.
   498  		//   2) Reached the select code but already in branch of deadline
   499  		//   cancellation. In this case it is locked on p.mu.Lock().
   500  		//   3) Not reached the select code and thus not reading yet from the
   501  		//   channel.
   502  		//
   503  		// For cases (2) and (3) we close the channel to signal that goroutine
   504  		// missed something and may want to retry (especially for case (3)).
   505  		//
   506  		// After that we taking a next waiter and repeat the same.
   507  		var ch *chan PT
   508  		p.changeState(func() Stats {
   509  			ch = p.waitQ.Remove(el) //nolint:scopelint
   510  
   511  			return p.stats()
   512  		})
   513  		select {
   514  		case *ch <- idle:
   515  			// Case (1).
   516  			return true
   517  
   518  		case <-p.done:
   519  			// Case (2) or (3).
   520  			close(*ch)
   521  
   522  		default:
   523  			// Case (2) or (3).
   524  			close(*ch)
   525  		}
   526  	}
   527  
   528  	return false
   529  }
   530  
   531  // p.mu must be held.
   532  func (p *Pool[PT, T]) removeIdle(item PT) itemInfo[PT, T] {
   533  	info, has := p.index[item]
   534  	if !has || info.idle == nil {
   535  		panic("inconsistent session client index")
   536  	}
   537  
   538  	p.changeState(func() Stats {
   539  		p.idle.Remove(info.idle)
   540  		info.idle = nil
   541  		p.index[item] = info
   542  
   543  		return p.stats()
   544  	})
   545  
   546  	return info
   547  }
   548  
   549  // p.mu must be held.
   550  func (p *Pool[PT, T]) pushIdle(item PT, now time.Time) {
   551  	info, has := p.index[item]
   552  	if !has {
   553  		panic("trying to store item created outside of the client")
   554  	}
   555  	if info.idle != nil {
   556  		panic("inconsistent item client index")
   557  	}
   558  
   559  	p.changeState(func() Stats {
   560  		info.lastUsage = now
   561  		info.idle = p.idle.PushBack(item)
   562  		p.index[item] = info
   563  
   564  		return p.stats()
   565  	})
   566  }
   567  
   568  const maxAttempts = 100
   569  
   570  func (p *Pool[PT, T]) getItem(ctx context.Context) (item PT, finalErr error) { //nolint:funlen
   571  	var (
   572  		start   = p.config.clock.Now()
   573  		attempt int
   574  		lastErr error
   575  	)
   576  
   577  	if onGet := p.config.trace.OnGet; onGet != nil {
   578  		onDone := onGet(&ctx,
   579  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).getItem"),
   580  		)
   581  		if onDone != nil {
   582  			defer func() {
   583  				onDone(item, attempt, finalErr)
   584  			}()
   585  		}
   586  	}
   587  
   588  	for ; attempt < maxAttempts; attempt++ {
   589  		select {
   590  		case <-p.done:
   591  			return nil, xerrors.WithStackTrace(errClosedPool)
   592  		default:
   593  		}
   594  
   595  		if item := xsync.WithLock(&p.mu, func() PT { //nolint:nestif
   596  			return p.removeFirstIdle()
   597  		}); item != nil {
   598  			if item.IsAlive() {
   599  				info := xsync.WithLock(&p.mu, func() itemInfo[PT, T] {
   600  					info, has := p.index[item]
   601  					if !has {
   602  						panic("no index for item")
   603  					}
   604  
   605  					*info.useCounter++
   606  
   607  					return info
   608  				})
   609  
   610  				if (p.config.itemUsageLimit > 0 && *info.useCounter > p.config.itemUsageLimit) ||
   611  					(p.config.idleTimeToLive > 0 && p.config.clock.Since(info.lastUsage) > p.config.idleTimeToLive) {
   612  					p.closeItem(ctx, item)
   613  					p.mu.WithLock(func() {
   614  						p.changeState(func() Stats {
   615  							delete(p.index, item)
   616  
   617  							return p.stats()
   618  						})
   619  					})
   620  
   621  					continue
   622  				}
   623  
   624  				return item, nil
   625  			}
   626  		}
   627  
   628  		item, err := p.createItem(ctx)
   629  		if item != nil {
   630  			return item, nil
   631  		}
   632  
   633  		if !isRetriable(err) {
   634  			return nil, xerrors.WithStackTrace(xerrors.Join(err, lastErr))
   635  		}
   636  
   637  		lastErr = err
   638  
   639  		item, err = p.waitFromCh(ctx)
   640  		if item != nil {
   641  			return item, nil
   642  		}
   643  
   644  		if err != nil && !isRetriable(err) {
   645  			return nil, xerrors.WithStackTrace(xerrors.Join(err, lastErr))
   646  		}
   647  
   648  		lastErr = err
   649  	}
   650  
   651  	p.mu.RLock()
   652  	defer p.mu.RUnlock()
   653  
   654  	return nil, xerrors.WithStackTrace(
   655  		fmt.Errorf("failed to get item from pool after %d attempts and %v, pool has %d items (%d busy, "+
   656  			"%d idle, %d create_in_progress): %w", attempt, p.config.clock.Since(start), len(p.index),
   657  			len(p.index)-p.idle.Len(), p.idle.Len(), p.createInProgress, lastErr,
   658  		),
   659  	)
   660  }
   661  
   662  //nolint:funlen
   663  func (p *Pool[PT, T]) waitFromCh(ctx context.Context) (item PT, finalErr error) {
   664  	var (
   665  		ch *chan PT
   666  		el *xlist.Element[*chan PT]
   667  	)
   668  
   669  	p.mu.WithLock(func() {
   670  		p.changeState(func() Stats {
   671  			ch = p.getWaitCh()
   672  			el = p.waitQ.PushBack(ch)
   673  
   674  			return p.stats()
   675  		})
   676  	})
   677  
   678  	if onWait := p.config.trace.onWait; onWait != nil {
   679  		onDone := onWait()
   680  		if onDone != nil {
   681  			defer func() {
   682  				onDone(item, finalErr)
   683  			}()
   684  		}
   685  	}
   686  
   687  	var deadliine <-chan time.Time
   688  	if timeout := p.config.createTimeout; timeout > 0 {
   689  		t := p.config.clock.NewTimer(timeout)
   690  		defer t.Stop()
   691  
   692  		deadliine = t.Chan()
   693  	}
   694  
   695  	select {
   696  	case <-p.done:
   697  		p.mu.WithLock(func() {
   698  			p.changeState(func() Stats {
   699  				p.waitQ.Remove(el)
   700  
   701  				return p.stats()
   702  			})
   703  		})
   704  
   705  		return nil, xerrors.WithStackTrace(errClosedPool)
   706  
   707  	case item, ok := <-*ch:
   708  		// Note that race may occur and some goroutine may try to write
   709  		// session into channel after it was enqueued but before it being
   710  		// read here. In that case we will receive nil here and will retry.
   711  		//
   712  		// The same way will work when some session become deleted - the
   713  		// nil value will be sent into the channel.
   714  		if ok {
   715  			// Put only filled and not closed channel back to the Client.
   716  			// That is, we need to avoid races on filling reused channel
   717  			// for the next waiter – session could be lost for a long time.
   718  			p.putWaitCh(ch)
   719  		}
   720  
   721  		return item, nil
   722  
   723  	case <-deadliine:
   724  		p.mu.WithLock(func() {
   725  			p.changeState(func() Stats {
   726  				p.waitQ.Remove(el)
   727  
   728  				return p.stats()
   729  			})
   730  		})
   731  
   732  		return nil, nil
   733  
   734  	case <-ctx.Done():
   735  		p.mu.WithLock(func() {
   736  			p.changeState(func() Stats {
   737  				p.waitQ.Remove(el)
   738  
   739  				return p.stats()
   740  			})
   741  		})
   742  
   743  		return nil, xerrors.WithStackTrace(ctx.Err())
   744  	}
   745  }
   746  
   747  // p.mu must be free.
   748  func (p *Pool[PT, T]) putItem(ctx context.Context, item PT) (finalErr error) {
   749  	if onPut := p.config.trace.OnPut; onPut != nil {
   750  		onDone := onPut(&ctx,
   751  			stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/pool.(*Pool).putItem"),
   752  			item,
   753  		)
   754  		if onDone != nil {
   755  			defer func() {
   756  				onDone(finalErr)
   757  			}()
   758  		}
   759  	}
   760  	select {
   761  	case <-p.done:
   762  		p.closeItem(ctx, item)
   763  		p.mu.WithLock(func() {
   764  			p.changeState(func() Stats {
   765  				delete(p.index, item)
   766  
   767  				return p.stats()
   768  			})
   769  		})
   770  
   771  		return xerrors.WithStackTrace(errClosedPool)
   772  	default:
   773  		p.mu.Lock()
   774  		defer p.mu.Unlock()
   775  
   776  		if !item.IsAlive() {
   777  			p.closeItem(ctx, item)
   778  			p.changeState(func() Stats {
   779  				delete(p.index, item)
   780  
   781  				return p.stats()
   782  			})
   783  
   784  			return xerrors.WithStackTrace(errItemIsNotAlive)
   785  		}
   786  
   787  		if p.idle.Len() >= p.config.limit {
   788  			p.closeItem(ctx, item)
   789  			p.changeState(func() Stats {
   790  				delete(p.index, item)
   791  
   792  				return p.stats()
   793  			})
   794  
   795  			return xerrors.WithStackTrace(errPoolIsOverflow)
   796  		}
   797  
   798  		if !p.notifyAboutIdle(item) {
   799  			p.pushIdle(item, p.config.clock.Now())
   800  		}
   801  
   802  		return nil
   803  	}
   804  }