github.com/matrixorigin/matrixone@v0.7.0/pkg/frontend/routine.go (about)

     1  // Copyright 2021 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package frontend
    16  
    17  import (
    18  	"context"
    19  	"sync"
    20  	"sync/atomic"
    21  	"time"
    22  
    23  	"github.com/fagongzi/goetty/v2"
    24  	"github.com/matrixorigin/matrixone/pkg/config"
    25  	"github.com/matrixorigin/matrixone/pkg/defines"
    26  	"github.com/matrixorigin/matrixone/pkg/util/metric"
    27  	"github.com/matrixorigin/matrixone/pkg/util/trace"
    28  )
    29  
    30  // Routine handles requests.
    31  // Read requests from the IOSession layer,
    32  // use the executor to handle requests, and response them.
    33  type Routine struct {
    34  	//protocol layer
    35  	protocol MysqlProtocol
    36  
    37  	//execution layer
    38  	executor CmdExecutor
    39  
    40  	cancelRoutineCtx  context.Context
    41  	cancelRoutineFunc context.CancelFunc
    42  
    43  	parameters *config.FrontendParameters
    44  
    45  	ses *Session
    46  
    47  	closeOnce sync.Once
    48  
    49  	inProcessRequest bool
    50  
    51  	cancelled atomic.Bool
    52  
    53  	connectionBeCounted atomic.Bool
    54  
    55  	mu sync.Mutex
    56  }
    57  
    58  func (rt *Routine) increaseCount(counter func()) {
    59  	if rt.connectionBeCounted.CompareAndSwap(false, true) {
    60  		if counter != nil {
    61  			counter()
    62  		}
    63  	}
    64  }
    65  
    66  func (rt *Routine) decreaseCount(counter func()) {
    67  	if rt.connectionBeCounted.CompareAndSwap(true, false) {
    68  		if counter != nil {
    69  			counter()
    70  		}
    71  	}
    72  }
    73  
    74  func (rt *Routine) setCancelled(b bool) bool {
    75  	return rt.cancelled.Swap(b)
    76  }
    77  
    78  func (rt *Routine) isCancelled() bool {
    79  	return rt.cancelled.Load()
    80  }
    81  
    82  func (rt *Routine) setInProcessRequest(b bool) {
    83  	rt.mu.Lock()
    84  	defer rt.mu.Unlock()
    85  	rt.inProcessRequest = b
    86  }
    87  
    88  // execCallbackInProcessRequestOnly denotes if inProcessRequest is true,
    89  // then the callback will be called.
    90  // It has used the mutex.
    91  func (rt *Routine) execCallbackBasedOnRequest(want bool, callback func()) {
    92  	rt.mu.Lock()
    93  	defer rt.mu.Unlock()
    94  	if rt.inProcessRequest == want {
    95  		if callback != nil {
    96  			callback()
    97  		}
    98  	}
    99  }
   100  
   101  func (rt *Routine) getCancelRoutineFunc() context.CancelFunc {
   102  	rt.mu.Lock()
   103  	defer rt.mu.Unlock()
   104  	return rt.cancelRoutineFunc
   105  }
   106  
   107  func (rt *Routine) getCancelRoutineCtx() context.Context {
   108  	rt.mu.Lock()
   109  	defer rt.mu.Unlock()
   110  	return rt.cancelRoutineCtx
   111  }
   112  
   113  func (rt *Routine) getProtocol() MysqlProtocol {
   114  	rt.mu.Lock()
   115  	defer rt.mu.Unlock()
   116  	return rt.protocol
   117  }
   118  
   119  func (rt *Routine) getCmdExecutor() CmdExecutor {
   120  	rt.mu.Lock()
   121  	defer rt.mu.Unlock()
   122  	return rt.executor
   123  }
   124  
   125  func (rt *Routine) getConnectionID() uint32 {
   126  	return rt.getProtocol().ConnectionID()
   127  }
   128  
   129  func (rt *Routine) getParameters() *config.FrontendParameters {
   130  	rt.mu.Lock()
   131  	defer rt.mu.Unlock()
   132  	return rt.parameters
   133  }
   134  
   135  func (rt *Routine) setSession(ses *Session) {
   136  	rt.mu.Lock()
   137  	defer rt.mu.Unlock()
   138  	rt.ses = ses
   139  }
   140  
   141  func (rt *Routine) getSession() *Session {
   142  	rt.mu.Lock()
   143  	defer rt.mu.Unlock()
   144  	return rt.ses
   145  }
   146  
   147  func (rt *Routine) handleRequest(req *Request) error {
   148  	var ses *Session
   149  	var routineCtx context.Context
   150  	var err error
   151  	var resp *Response
   152  	var quit bool
   153  	reqBegin := time.Now()
   154  	routineCtx = rt.getCancelRoutineCtx()
   155  	parameters := rt.getParameters()
   156  	mpi := rt.getProtocol()
   157  	mpi.SetSequenceID(req.seq)
   158  	cancelRequestCtx, cancelRequestFunc := context.WithTimeout(routineCtx, parameters.SessionTimeout.Duration)
   159  	executor := rt.getCmdExecutor()
   160  	executor.SetCancelFunc(cancelRequestFunc)
   161  	ses = rt.getSession()
   162  	ses.MakeProfile()
   163  	tenant := ses.GetTenantInfo()
   164  	tenantCtx := context.WithValue(cancelRequestCtx, defines.TenantIDKey{}, tenant.GetTenantID())
   165  	tenantCtx = context.WithValue(tenantCtx, defines.UserIDKey{}, tenant.GetUserID())
   166  	tenantCtx = context.WithValue(tenantCtx, defines.RoleIDKey{}, tenant.GetDefaultRoleID())
   167  	tenantCtx = trace.ContextWithSpanContext(tenantCtx, trace.SpanContextWithID(trace.TraceID(ses.uuid), trace.SpanKindSession))
   168  	ses.SetRequestContext(tenantCtx)
   169  	executor.SetSession(rt.getSession())
   170  
   171  	rt.increaseCount(func() {
   172  		metric.ConnectionCounter(ses.GetTenantInfo().GetTenant()).Inc()
   173  	})
   174  
   175  	if resp, err = executor.ExecRequest(tenantCtx, ses, req); err != nil {
   176  		logErrorf(ses.GetConciseProfile(), "rt execute request failed. error:%v \n", err)
   177  	}
   178  
   179  	if resp != nil {
   180  		if err = rt.getProtocol().SendResponse(tenantCtx, resp); err != nil {
   181  			logErrorf(ses.GetConciseProfile(), "rt send response failed %v. error:%v ", resp, err)
   182  		}
   183  	}
   184  
   185  	logDebugf(ses.GetConciseProfile(), "the time of handling the request %s", time.Since(reqBegin).String())
   186  
   187  	cancelRequestFunc()
   188  
   189  	//check the connection has been already canceled or not.
   190  	select {
   191  	case <-routineCtx.Done():
   192  		quit = true
   193  	default:
   194  	}
   195  
   196  	quit = quit || rt.isCancelled()
   197  
   198  	if quit {
   199  		rt.decreaseCount(func() {
   200  			metric.ConnectionCounter(ses.GetTenantInfo().GetTenant()).Dec()
   201  		})
   202  
   203  		//ensure cleaning the transaction
   204  		logErrorf(ses.GetConciseProfile(), "rollback the txn.")
   205  		err = ses.TxnRollback()
   206  		if err != nil {
   207  			logErrorf(ses.GetConciseProfile(), "rollback txn failed.error:%v", err)
   208  		}
   209  
   210  		//close the network connection
   211  		proto := rt.getProtocol()
   212  		if proto != nil {
   213  			proto.Quit()
   214  		}
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // killQuery if there is a running query, just cancel it.
   221  func (rt *Routine) killQuery(killMyself bool, statementId string) {
   222  	if !killMyself {
   223  		executor := rt.getCmdExecutor()
   224  		if executor != nil {
   225  			//just cancel the request context.
   226  			executor.CancelRequest()
   227  		}
   228  	}
   229  }
   230  
   231  // killConnection close the network connection
   232  // myself: true -- the client kill itself.
   233  // myself: false -- the client kill another connection.
   234  func (rt *Routine) killConnection(killMyself bool) {
   235  	//Case 1: kill the connection itself. Do not close the network connection here.
   236  	//label the connection with the cancelled tag
   237  	//if it was cancelled, do nothing
   238  	if rt.setCancelled(true) {
   239  		return
   240  	}
   241  
   242  	//Case 2: kill another connection. Close the network here.
   243  	//    if the connection is processing the request, the response may be dropped.
   244  	//    if the connection is not processing the request, it has no effect.
   245  	if !killMyself {
   246  		//If it is in processing the request, cancel the root context of the connection.
   247  		//At the same time, it cancels all the contexts
   248  		//(includes the request context) derived from the root context.
   249  		//After the context is cancelled. In handleRequest, the network
   250  		//will be closed finally.
   251  		cancel := rt.getCancelRoutineFunc()
   252  		if cancel != nil {
   253  			cancel()
   254  		}
   255  
   256  		//If it is in processing the request, it responds to the client normally
   257  		//before closing the network to avoid the mysql client to be hung.
   258  		closeConn := func() {
   259  			//If it is not in processing the request, just close the network
   260  			proto := rt.protocol
   261  			if proto != nil {
   262  				proto.Quit()
   263  			}
   264  		}
   265  
   266  		rt.execCallbackBasedOnRequest(false, closeConn)
   267  	}
   268  }
   269  
   270  // cleanup When the io is closed, the cleanup will be called in callback Closed().
   271  // cleanup releases the resources only once.
   272  // both the client and the server can close the connection.
   273  func (rt *Routine) cleanup() {
   274  	//step 1: cancel the query if there is a running query.
   275  	//step 2: close the connection.
   276  	rt.closeOnce.Do(func() {
   277  		//step A: release the mempool related to the session
   278  		ses := rt.getSession()
   279  		if ses != nil {
   280  			ses.Dispose()
   281  		}
   282  
   283  		//step B: cancel the query
   284  		rt.killQuery(false, "")
   285  
   286  		//step C: cancel the root context of the connection.
   287  		//At the same time, it cancels all the contexts
   288  		//(includes the request context) derived from the root context.
   289  		cancel := rt.getCancelRoutineFunc()
   290  		if cancel != nil {
   291  			cancel()
   292  		}
   293  	})
   294  }
   295  
   296  func NewRoutine(ctx context.Context, protocol MysqlProtocol, executor CmdExecutor, parameters *config.FrontendParameters, rs goetty.IOSession) *Routine {
   297  	ctx = trace.Generate(ctx) // fill span{trace_id} in ctx
   298  	cancelRoutineCtx, cancelRoutineFunc := context.WithCancel(ctx)
   299  	ri := &Routine{
   300  		protocol:          protocol,
   301  		executor:          executor,
   302  		cancelRoutineCtx:  cancelRoutineCtx,
   303  		cancelRoutineFunc: cancelRoutineFunc,
   304  		parameters:        parameters,
   305  	}
   306  
   307  	return ri
   308  }