github.com/ronaksoft/rony@v0.16.26-0.20230807065236-1743dbfe6959/edge/request_context.go (about) 1 package edge 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "github.com/ronaksoft/rony/registry" 9 "go.opentelemetry.io/otel/attribute" 10 11 semconv "go.opentelemetry.io/otel/semconv/v1.7.0" 12 13 "github.com/ronaksoft/rony" 14 "github.com/ronaksoft/rony/errors" 15 "github.com/ronaksoft/rony/log" 16 "github.com/ronaksoft/rony/tools" 17 "go.opentelemetry.io/otel/trace" 18 "google.golang.org/protobuf/proto" 19 ) 20 21 // RequestCtx holds the context of an RPC handler 22 type RequestCtx struct { 23 dispatchCtx *DispatchCtx 24 edge *Server 25 reqID uint64 26 nextChan chan struct{} 27 quickReturn bool 28 stop bool 29 err *rony.Error 30 // cfg holds all cancel functions which will be called at the 31 // end of the lifecycle of the RequestCtx 32 ctx context.Context 33 cf func() 34 } 35 36 func newRequestCtx() *RequestCtx { 37 return &RequestCtx{ 38 nextChan: make(chan struct{}, 1), 39 } 40 } 41 42 // NextCanGo is useful only if the main request is rony.MessageContainer with SyncRun flag set to TRUE. 43 // Then by calling this function it lets the next request (if any) executed concurrently. 44 func (ctx *RequestCtx) NextCanGo() { 45 if ctx.quickReturn { 46 ctx.nextChan <- struct{}{} 47 } 48 } 49 50 func (ctx *RequestCtx) ConnID() uint64 { 51 if ctx.dispatchCtx.Conn() != nil { 52 return ctx.dispatchCtx.Conn().ConnID() 53 } 54 55 return 0 56 } 57 58 func (ctx *RequestCtx) Conn() rony.Conn { 59 return ctx.dispatchCtx.Conn() 60 } 61 62 func (ctx *RequestCtx) ReqID() uint64 { 63 return ctx.reqID 64 } 65 66 func (ctx *RequestCtx) ServerID() string { 67 return string(ctx.dispatchCtx.serverID) 68 } 69 70 func (ctx *RequestCtx) Kind() MessageKind { 71 return ctx.dispatchCtx.kind 72 } 73 74 func (ctx *RequestCtx) StopExecution() { 75 ctx.stop = true 76 } 77 78 func (ctx *RequestCtx) Stopped() bool { 79 return ctx.stop 80 } 81 82 func (ctx *RequestCtx) Set(key string, v interface{}) { 83 ctx.dispatchCtx.mtx.Lock() 84 ctx.dispatchCtx.kv[key] = v 85 ctx.dispatchCtx.mtx.Unlock() 86 } 87 88 func (ctx *RequestCtx) Get(key string) interface{} { 89 return ctx.dispatchCtx.Get(key) 90 } 91 92 func (ctx *RequestCtx) GetBytes(key string, defaultValue []byte) []byte { 93 return ctx.dispatchCtx.GetBytes(key, defaultValue) 94 } 95 96 func (ctx *RequestCtx) GetString(key string, defaultValue string) string { 97 return ctx.dispatchCtx.GetString(key, defaultValue) 98 } 99 100 func (ctx *RequestCtx) GetInt64(key string, defaultValue int64) int64 { 101 return ctx.dispatchCtx.GetInt64(key, defaultValue) 102 } 103 104 func (ctx *RequestCtx) GetBool(key string) bool { 105 return ctx.dispatchCtx.GetBool(key) 106 } 107 108 func (ctx *RequestCtx) pushRedirect(reason rony.RedirectReason, replicaSet uint64) { 109 r := rony.PoolRedirect.Get() 110 r.Reason = reason 111 r.WaitInSec = 0 112 113 members := ctx.Cluster().MembersByReplicaSet(replicaSet) 114 for _, m := range members { 115 ni := m.Proto(rony.PoolEdge.Get()) 116 r.Edges = append(r.Edges, ni) 117 } 118 119 ctx.PushMessage(rony.C_Redirect, r) 120 rony.PoolRedirect.Put(r) 121 ctx.StopExecution() 122 } 123 124 // PushRedirectSession redirects the client to another server with replicaSet for the rest 125 // of the request for this session. The interpretation of session is defined by the application 126 // and does not have any side effect on internal states of the Edge server. 127 // This function call StopExecution internally and further handlers would not be called. 128 func (ctx *RequestCtx) PushRedirectSession(replicaSet uint64) { 129 ctx.pushRedirect(rony.RedirectReason_ReplicaSetSession, replicaSet) 130 } 131 132 // PushRedirectRequest redirects the client to another server with replicaSet for only 133 // this request. This function call StopExecution internally and further handlers would 134 // not be called. 135 func (ctx *RequestCtx) PushRedirectRequest(replicaSet uint64) { 136 ctx.pushRedirect(rony.RedirectReason_ReplicaSetRequest, replicaSet) 137 } 138 139 // PushMessage is a wrapper func for PushCustomMessage. 140 func (ctx *RequestCtx) PushMessage(constructor uint64, proto proto.Message) { 141 ctx.PushCustomMessage(ctx.ReqID(), constructor, proto) 142 } 143 144 // PushCustomMessage would do different actions based on the Conn. If connection is persistent (i.e. Websocket) 145 // then Rony sends the message down to the wire real time. If the connection is not persistent 146 // (i.e. HTTP) then Rony push the message into a temporary buffer and at the end of the life-time 147 // of the RequestCtx pushes the message as response. 148 // If there was multiple message in the buffer then Rony creates a MessageContainer and wrap 149 // all those messages in that container. Client needs to unwrap MessageContainer when ever they 150 // see it in the response. 151 func (ctx *RequestCtx) PushCustomMessage( 152 requestID uint64, constructor uint64, message proto.Message, kvs ...*rony.KeyValue, 153 ) { 154 envelope := rony.PoolMessageEnvelope.Get() 155 envelope.Fill(requestID, constructor, message, kvs...) 156 157 switch ctx.Kind() { 158 case TunnelMessage: 159 ctx.dispatchCtx.BufferPush(envelope) 160 case GatewayMessage: 161 if ctx.edge.tracer != nil { 162 trace.SpanFromContext(ctx.ctx). 163 AddEvent("message", 164 trace.WithAttributes( 165 semconv.MessageTypeSent, 166 semconv.MessageIDKey.Int64(int64(requestID)), 167 attribute.String("rony.constructor", registry.C(constructor)), 168 ), 169 ) 170 } 171 172 if ctx.Conn().Persistent() { 173 _ = ctx.edge.dispatcher.Encode(ctx.dispatchCtx.conn, ctx.dispatchCtx.streamID, envelope) 174 rony.PoolMessageEnvelope.Put(envelope) 175 } else { 176 ctx.dispatchCtx.BufferPush(envelope) 177 } 178 } 179 } 180 181 func (ctx *RequestCtx) PushError(err *rony.Error) { 182 ctx.err = err.Clone() 183 ctx.PushMessage(rony.C_Error, err) 184 ctx.stop = true 185 } 186 187 func (ctx *RequestCtx) PushCustomError(code, item string, desc string) { 188 ctx.PushMessage( 189 rony.C_Error, 190 &rony.Error{ 191 Code: code, 192 Items: item, 193 Description: desc, 194 }, 195 ) 196 ctx.stop = true 197 } 198 199 func (ctx *RequestCtx) Error() *rony.Error { 200 return ctx.err 201 } 202 203 func (ctx *RequestCtx) Cluster() rony.Cluster { 204 return ctx.edge.cluster 205 } 206 207 func (ctx *RequestCtx) ClusterEdges(replicaSet uint64, edges *rony.Edges) (*rony.Edges, error) { 208 req := rony.PoolMessageEnvelope.Get() 209 defer rony.PoolMessageEnvelope.Put(req) 210 res := rony.PoolMessageEnvelope.Get() 211 defer rony.PoolMessageEnvelope.Put(res) 212 req.Fill(tools.RandomUint64(0), rony.C_GetAllNodes, &rony.GetAllNodes{}) 213 err := ctx.TunnelRequest(replicaSet, req, res) 214 if err != nil { 215 return nil, err 216 } 217 switch res.Constructor { 218 case rony.C_Edges: 219 if edges == nil { 220 edges = &rony.Edges{} 221 } 222 _ = edges.Unmarshal(res.GetMessage()) 223 224 return edges, nil 225 case rony.C_Error: 226 x := &rony.Error{} 227 _ = x.Unmarshal(res.GetMessage()) 228 229 return nil, x 230 default: 231 return nil, errors.ErrUnexpectedTunnelResponse 232 } 233 } 234 235 func (ctx *RequestCtx) TryTunnelRequest( 236 attempts int, retryWait time.Duration, replicaSet uint64, 237 req, res *rony.MessageEnvelope, 238 ) error { 239 return ctx.edge.TryTunnelRequest(attempts, retryWait, replicaSet, req, res) 240 } 241 242 func (ctx *RequestCtx) TunnelRequest(replicaSet uint64, req, res *rony.MessageEnvelope) error { 243 return ctx.edge.TryTunnelRequest(1, 0, replicaSet, req, res) 244 } 245 246 // Log returns a logger 247 func (ctx *RequestCtx) Log() log.Logger { 248 return ctx.edge.logger 249 } 250 251 // Span returns a tracer span. Don't End the span, since it will be 252 // closed automatically at the end of RequestCtx lifecycle. 253 func (ctx *RequestCtx) Span() trace.Span { 254 return trace.SpanFromContext(ctx.ctx) 255 } 256 257 func (ctx *RequestCtx) ReplicaSet() uint64 { 258 if ctx.edge.cluster == nil { 259 return 0 260 } 261 262 return ctx.edge.cluster.ReplicaSet() 263 } 264 265 func (ctx *RequestCtx) Router() rony.Router { 266 return ctx.edge.router 267 } 268 269 func (ctx *RequestCtx) Context() context.Context { 270 return ctx.ctx 271 } 272 273 var requestCtxPool = sync.Pool{} 274 275 func acquireRequestCtx(dispatchCtx *DispatchCtx, quickReturn bool) *RequestCtx { 276 var ctx *RequestCtx 277 if v := requestCtxPool.Get(); v == nil { 278 ctx = newRequestCtx() 279 } else { 280 ctx = v.(*RequestCtx) 281 } 282 ctx.stop = false 283 ctx.quickReturn = quickReturn 284 ctx.dispatchCtx = dispatchCtx 285 ctx.edge = dispatchCtx.edge 286 ctx.err = nil 287 ctx.ctx, ctx.cf = context.WithCancel(dispatchCtx.ctx) 288 289 return ctx 290 } 291 292 func releaseRequestCtx(ctx *RequestCtx) { 293 // Just to make sure channel is empty, or empty it if not 294 select { 295 case <-ctx.nextChan: 296 default: 297 } 298 299 // call cancel func 300 ctx.cf() 301 302 // reset request id 303 ctx.reqID = 0 304 305 // Put back into the pool 306 requestCtxPool.Put(ctx) 307 }