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

     1  package session
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  	"time"
     7  
     8  	"github.com/ydb-platform/ydb-go-genproto/Ydb_Query_V1"
     9  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Query"
    10  	"google.golang.org/grpc"
    11  
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/conn"
    13  	balancerContext "github.com/ydb-platform/ydb-go-sdk/v3/internal/endpoint"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/pool"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync"
    19  	"github.com/ydb-platform/ydb-go-sdk/v3/query"
    20  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    21  )
    22  
    23  type (
    24  	Core interface {
    25  		query.SessionInfo
    26  		pool.Item
    27  
    28  		SetStatus(code Status)
    29  	}
    30  	core struct {
    31  		cc     grpc.ClientConnInterface
    32  		Client Ydb_Query_V1.QueryServiceClient
    33  		Trace  *trace.Query
    34  
    35  		deleteTimeout time.Duration
    36  		id            string
    37  		nodeID        uint32
    38  		status        atomic.Uint32
    39  		closeOnce     func(ctx context.Context) error
    40  		checks        []func(s *core) bool
    41  	}
    42  )
    43  
    44  func (c *core) ID() string {
    45  	return c.id
    46  }
    47  
    48  func (c *core) NodeID() uint32 {
    49  	return c.nodeID
    50  }
    51  
    52  func (c *core) statusCode() Status {
    53  	return Status(c.status.Load())
    54  }
    55  
    56  func (c *core) SetStatus(status Status) {
    57  	switch Status(c.status.Load()) {
    58  	case StatusClosed, StatusError:
    59  		// nop
    60  	default:
    61  		c.status.Store(uint32(status))
    62  	}
    63  }
    64  
    65  func (c *core) Status() string {
    66  	return c.statusCode().String()
    67  }
    68  
    69  type Option func(*core)
    70  
    71  func WithConn(cc grpc.ClientConnInterface) Option {
    72  	return func(c *core) {
    73  		c.cc = cc
    74  	}
    75  }
    76  
    77  func WithDeleteTimeout(deleteTimeout time.Duration) Option {
    78  	return func(c *core) {
    79  		c.deleteTimeout = deleteTimeout
    80  	}
    81  }
    82  
    83  func WithTrace(t *trace.Query) Option {
    84  	return func(c *core) {
    85  		c.Trace = c.Trace.Compose(t)
    86  	}
    87  }
    88  
    89  func IsAlive(status Status) bool {
    90  	switch status {
    91  	case StatusClosed, StatusClosing, StatusError:
    92  		return false
    93  	default:
    94  		return true
    95  	}
    96  }
    97  
    98  func Open(
    99  	ctx context.Context, client Ydb_Query_V1.QueryServiceClient, opts ...Option,
   100  ) (_ *core, finalErr error) {
   101  	core := &core{
   102  		Client: client,
   103  		Trace:  &trace.Query{},
   104  		checks: []func(s *core) bool{
   105  			func(s *core) bool {
   106  				return IsAlive(Status(s.status.Load()))
   107  			},
   108  		},
   109  	}
   110  
   111  	for _, opt := range opts {
   112  		if opt != nil {
   113  			opt(core)
   114  		}
   115  	}
   116  
   117  	onDone := trace.QueryOnSessionCreate(core.Trace, &ctx,
   118  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query/session.Open"),
   119  	)
   120  	defer func() {
   121  		if finalErr == nil {
   122  			onDone(core, nil)
   123  		} else {
   124  			onDone(nil, finalErr)
   125  		}
   126  	}()
   127  
   128  	response, err := client.CreateSession(ctx, &Ydb_Query.CreateSessionRequest{})
   129  	if err != nil {
   130  		return nil, xerrors.WithStackTrace(err)
   131  	}
   132  
   133  	if core.cc != nil {
   134  		core.Client = Ydb_Query_V1.NewQueryServiceClient(
   135  			conn.WithContextModifier(core.cc, func(ctx context.Context) context.Context {
   136  				return balancerContext.WithNodeID(ctx, core.NodeID())
   137  			}),
   138  		)
   139  	}
   140  
   141  	core.id = response.GetSessionId()
   142  	core.nodeID = uint32(response.GetNodeId())
   143  
   144  	err = core.attach(ctx)
   145  	if err != nil {
   146  		_ = core.deleteSession(ctx)
   147  
   148  		return nil, xerrors.WithStackTrace(err)
   149  	}
   150  
   151  	core.SetStatus(StatusIdle)
   152  
   153  	return core, nil
   154  }
   155  
   156  func (c *core) attach(ctx context.Context) (finalErr error) {
   157  	onDone := trace.QueryOnSessionAttach(c.Trace, &ctx,
   158  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query/session.(*core).attach"),
   159  		c,
   160  	)
   161  	defer func() {
   162  		onDone(finalErr)
   163  	}()
   164  
   165  	attachCtx, cancelAttach := xcontext.WithCancel(xcontext.ValueOnly(ctx))
   166  	defer func() {
   167  		if finalErr != nil {
   168  			cancelAttach()
   169  		}
   170  	}()
   171  
   172  	attach, err := c.Client.AttachSession(attachCtx, &Ydb_Query.AttachSessionRequest{
   173  		SessionId: c.id,
   174  	})
   175  	if err != nil {
   176  		return xerrors.WithStackTrace(err)
   177  	}
   178  
   179  	_, err = attach.Recv()
   180  	if err != nil {
   181  		return xerrors.WithStackTrace(err)
   182  	}
   183  
   184  	c.closeOnce = xsync.OnceFunc(c.closeAndDelete(cancelAttach))
   185  
   186  	go func() {
   187  		defer func() {
   188  			_ = c.closeOnce(xcontext.ValueOnly(ctx))
   189  		}()
   190  
   191  		for c.IsAlive() {
   192  			if _, recvErr := attach.Recv(); recvErr != nil {
   193  				return
   194  			}
   195  		}
   196  	}()
   197  
   198  	return nil
   199  }
   200  
   201  func (c *core) closeAndDelete(cancelAttach context.CancelFunc) func(ctx context.Context) (err error) {
   202  	return func(ctx context.Context) (err error) {
   203  		defer cancelAttach()
   204  
   205  		c.SetStatus(StatusClosing)
   206  		defer c.SetStatus(StatusClosed)
   207  
   208  		var cancel context.CancelFunc
   209  		if d := c.deleteTimeout; d > 0 {
   210  			ctx, cancel = xcontext.WithTimeout(ctx, d)
   211  		} else {
   212  			ctx, cancel = xcontext.WithCancel(ctx)
   213  		}
   214  		defer cancel()
   215  
   216  		if err = c.deleteSession(ctx); err != nil {
   217  			return xerrors.WithStackTrace(err)
   218  		}
   219  
   220  		return nil
   221  	}
   222  }
   223  
   224  func (c *core) deleteSession(ctx context.Context) (finalErr error) {
   225  	onDone := trace.QueryOnSessionDelete(c.Trace, &ctx,
   226  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/query/session.(*core).deleteSession"),
   227  		c,
   228  	)
   229  	defer func() {
   230  		onDone(finalErr)
   231  	}()
   232  
   233  	_, err := c.Client.DeleteSession(ctx,
   234  		&Ydb_Query.DeleteSessionRequest{
   235  			SessionId: c.id,
   236  		},
   237  	)
   238  	if err != nil {
   239  		return xerrors.WithStackTrace(err)
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  func (c *core) IsAlive() bool {
   246  	for _, check := range c.checks {
   247  		if !check(c) {
   248  			return false
   249  		}
   250  	}
   251  
   252  	return true
   253  }
   254  
   255  func (c *core) Close(ctx context.Context) (err error) {
   256  	if c.closeOnce != nil {
   257  		return c.closeOnce(ctx)
   258  	}
   259  
   260  	return nil
   261  }