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 }