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  }