github.com/matrixorigin/matrixone@v1.2.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  	"fmt"
    20  	"sync"
    21  	"sync/atomic"
    22  	"time"
    23  
    24  	"github.com/fagongzi/goetty/v2"
    25  	"go.uber.org/zap"
    26  
    27  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    28  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    29  	"github.com/matrixorigin/matrixone/pkg/config"
    30  	"github.com/matrixorigin/matrixone/pkg/defines"
    31  	"github.com/matrixorigin/matrixone/pkg/pb/query"
    32  	"github.com/matrixorigin/matrixone/pkg/util/metric"
    33  	v2 "github.com/matrixorigin/matrixone/pkg/util/metric/v2"
    34  	"github.com/matrixorigin/matrixone/pkg/util/status"
    35  	"github.com/matrixorigin/matrixone/pkg/util/trace"
    36  )
    37  
    38  // Routine handles requests.
    39  // Read requests from the IOSession layer,
    40  // use the executor to handle requests, and response them.
    41  type Routine struct {
    42  	//protocol layer
    43  	protocol MysqlProtocol
    44  
    45  	cancelRoutineCtx  context.Context
    46  	cancelRoutineFunc context.CancelFunc
    47  	cancelRequestFunc context.CancelFunc
    48  
    49  	parameters *config.FrontendParameters
    50  
    51  	ses *Session
    52  
    53  	closeOnce sync.Once
    54  
    55  	inProcessRequest bool
    56  
    57  	cancelled atomic.Bool
    58  
    59  	connectionBeCounted atomic.Bool
    60  
    61  	mu sync.Mutex
    62  
    63  	// the id of goroutine that executes the request
    64  	goroutineID uint64
    65  
    66  	restricted atomic.Bool
    67  
    68  	printInfoOnce bool
    69  
    70  	mc *migrateController
    71  }
    72  
    73  func (rt *Routine) needPrintSessionInfo() bool {
    74  	if rt.printInfoOnce {
    75  		rt.printInfoOnce = false
    76  		return true
    77  	}
    78  	return false
    79  }
    80  
    81  func (rt *Routine) setResricted(val bool) {
    82  	rt.restricted.Store(val)
    83  }
    84  
    85  func (rt *Routine) isRestricted() bool {
    86  	return rt.restricted.Load()
    87  }
    88  
    89  func (rt *Routine) increaseCount(counter func()) {
    90  	if rt.connectionBeCounted.CompareAndSwap(false, true) {
    91  		if counter != nil {
    92  			counter()
    93  		}
    94  	}
    95  }
    96  
    97  func (rt *Routine) decreaseCount(counter func()) {
    98  	if rt.connectionBeCounted.CompareAndSwap(true, false) {
    99  		if counter != nil {
   100  			counter()
   101  		}
   102  	}
   103  }
   104  
   105  func (rt *Routine) setCancelled(b bool) bool {
   106  	return rt.cancelled.Swap(b)
   107  }
   108  
   109  func (rt *Routine) isCancelled() bool {
   110  	return rt.cancelled.Load()
   111  }
   112  
   113  func (rt *Routine) setInProcessRequest(b bool) {
   114  	rt.mu.Lock()
   115  	defer rt.mu.Unlock()
   116  	rt.inProcessRequest = b
   117  }
   118  
   119  // execCallbackInProcessRequestOnly denotes if inProcessRequest is true,
   120  // then the callback will be called.
   121  // It has used the mutex.
   122  func (rt *Routine) execCallbackBasedOnRequest(want bool, callback func()) {
   123  	rt.mu.Lock()
   124  	defer rt.mu.Unlock()
   125  	if rt.inProcessRequest == want {
   126  		if callback != nil {
   127  			callback()
   128  		}
   129  	}
   130  }
   131  
   132  func (rt *Routine) releaseRoutineCtx() {
   133  	rt.mu.Lock()
   134  	defer rt.mu.Unlock()
   135  	if rt.cancelRoutineFunc != nil {
   136  		rt.cancelRoutineFunc()
   137  	}
   138  }
   139  
   140  func (rt *Routine) getCancelRoutineCtx() context.Context {
   141  	rt.mu.Lock()
   142  	defer rt.mu.Unlock()
   143  	return rt.cancelRoutineCtx
   144  }
   145  
   146  func (rt *Routine) getProtocol() MysqlProtocol {
   147  	rt.mu.Lock()
   148  	defer rt.mu.Unlock()
   149  	return rt.protocol
   150  }
   151  
   152  func (rt *Routine) getConnectionID() uint32 {
   153  	return rt.getProtocol().ConnectionID()
   154  }
   155  
   156  func (rt *Routine) updateGoroutineId() {
   157  	if rt.goroutineID == 0 {
   158  		rt.goroutineID = GetRoutineId()
   159  	}
   160  }
   161  
   162  func (rt *Routine) getGoroutineId() uint64 {
   163  	return rt.goroutineID
   164  }
   165  
   166  func (rt *Routine) getParameters() *config.FrontendParameters {
   167  	rt.mu.Lock()
   168  	defer rt.mu.Unlock()
   169  	return rt.parameters
   170  }
   171  
   172  func (rt *Routine) setSession(ses *Session) {
   173  	rt.mu.Lock()
   174  	defer rt.mu.Unlock()
   175  	rt.ses = ses
   176  }
   177  
   178  func (rt *Routine) getSession() *Session {
   179  	rt.mu.Lock()
   180  	defer rt.mu.Unlock()
   181  	return rt.ses
   182  }
   183  
   184  func (rt *Routine) setCancelRequestFunc(cf context.CancelFunc) {
   185  	rt.mu.Lock()
   186  	defer rt.mu.Unlock()
   187  	rt.cancelRequestFunc = cf
   188  }
   189  
   190  func (rt *Routine) cancelRequestCtx() {
   191  	rt.mu.Lock()
   192  	defer rt.mu.Unlock()
   193  	if rt.cancelRequestFunc != nil {
   194  		rt.cancelRequestFunc()
   195  	}
   196  }
   197  
   198  func (rt *Routine) reportSystemStatus() (r bool) {
   199  	ss := rt.ses
   200  	if ss == nil {
   201  		return
   202  	}
   203  	rm := ss.getRoutineManager()
   204  	if rm == nil {
   205  		return
   206  	}
   207  
   208  	now := time.Now()
   209  	defer func() {
   210  		if r {
   211  			rm.reportSystemStatusTime.Store(&now)
   212  		}
   213  	}()
   214  	last := rm.reportSystemStatusTime.Load()
   215  	if last == nil {
   216  		r = true
   217  		return
   218  	}
   219  	if now.Sub(*last) > time.Minute {
   220  		r = true
   221  		return
   222  	}
   223  	return
   224  }
   225  
   226  func (rt *Routine) handleRequest(req *Request) error {
   227  	var routineCtx context.Context
   228  	var err error
   229  	var resp *Response
   230  	var quit bool
   231  
   232  	ses := rt.getSession()
   233  
   234  	execCtx := ExecCtx{
   235  		ses: ses,
   236  	}
   237  
   238  	v2.StartHandleRequestCounter.Inc()
   239  	defer func() {
   240  		v2.EndHandleRequestCounter.Inc()
   241  	}()
   242  
   243  	reqBegin := time.Now()
   244  	var span trace.Span
   245  	routineCtx, span = trace.Start(rt.getCancelRoutineCtx(), "Routine.handleRequest",
   246  		trace.WithHungThreshold(30*time.Minute),
   247  		trace.WithProfileGoroutine(),
   248  		trace.WithProfileSystemStatus(func() ([]byte, error) {
   249  			ss, ok := runtime.ProcessLevelRuntime().GetGlobalVariables(runtime.StatusServer)
   250  			if !ok {
   251  				return nil, nil
   252  			}
   253  			if !rt.reportSystemStatus() {
   254  				return nil, nil
   255  			}
   256  			data, err := ss.(*status.Server).Dump()
   257  			return data, err
   258  		}),
   259  	)
   260  	defer span.End()
   261  
   262  	parameters := rt.getParameters()
   263  	//all offspring related to the request inherit the txnCtx
   264  	cancelRequestCtx, cancelRequestFunc := context.WithTimeout(ses.GetTxnHandler().GetTxnCtx(), parameters.SessionTimeout.Duration)
   265  	rt.setCancelRequestFunc(cancelRequestFunc)
   266  	ses.UpdateDebugString()
   267  
   268  	if rt.needPrintSessionInfo() {
   269  		logInfof(ses.GetDebugString(), "mo received first request")
   270  	}
   271  
   272  	tenant := ses.GetTenantInfo()
   273  	nodeCtx := cancelRequestCtx
   274  	if ses.getRoutineManager().baseService != nil {
   275  		nodeCtx = context.WithValue(cancelRequestCtx, defines.NodeIDKey{}, ses.getRoutineManager().baseService.ID())
   276  	}
   277  	tenantCtx := defines.AttachAccount(nodeCtx, tenant.GetTenantID(), tenant.GetUserID(), tenant.GetDefaultRoleID())
   278  
   279  	rt.increaseCount(func() {
   280  		metric.ConnectionCounter(ses.GetTenantInfo().GetTenant()).Inc()
   281  	})
   282  
   283  	execCtx.reqCtx = tenantCtx
   284  	if resp, err = ExecRequest(ses, &execCtx, req); err != nil {
   285  		if !skipClientQuit(err.Error()) {
   286  			logError(ses, ses.GetDebugString(),
   287  				"Failed to execute request",
   288  				zap.Error(err))
   289  		}
   290  	}
   291  
   292  	if resp != nil {
   293  		if err = rt.getProtocol().SendResponse(tenantCtx, resp); err != nil {
   294  			logError(ses, ses.GetDebugString(),
   295  				"Failed to send response",
   296  				zap.String("response", fmt.Sprintf("%v", resp)),
   297  				zap.Error(err))
   298  		}
   299  	}
   300  
   301  	logDebugf(ses.GetDebugString(), "the time of handling the request %s", time.Since(reqBegin).String())
   302  
   303  	cancelRequestFunc()
   304  
   305  	//check the connection has been already canceled or not.
   306  	select {
   307  	case <-routineCtx.Done():
   308  		quit = true
   309  	default:
   310  	}
   311  
   312  	quit = quit || rt.isCancelled()
   313  
   314  	if quit {
   315  		rt.decreaseCount(func() {
   316  			metric.ConnectionCounter(ses.GetTenantInfo().GetTenant()).Dec()
   317  		})
   318  
   319  		//ensure cleaning the transaction
   320  		logError(ses, ses.GetDebugString(), "rollback the txn.")
   321  		tempExecCtx := ExecCtx{
   322  			ses:    ses,
   323  			txnOpt: FeTxnOption{byRollback: true},
   324  		}
   325  		err = ses.GetTxnHandler().Rollback(&tempExecCtx)
   326  		if err != nil {
   327  			logError(ses, ses.GetDebugString(),
   328  				"Failed to rollback txn",
   329  				zap.Error(err))
   330  		}
   331  
   332  		//close the network connection
   333  		proto := rt.getProtocol()
   334  		if proto != nil {
   335  			proto.Quit()
   336  		}
   337  	}
   338  
   339  	return err
   340  }
   341  
   342  // killQuery if there is a running query, just cancel it.
   343  func (rt *Routine) killQuery(killMyself bool, statementId string) {
   344  	if !killMyself {
   345  		//1,cancel request ctx
   346  		rt.cancelRequestCtx()
   347  		//2.update execute state
   348  		ses := rt.getSession()
   349  		if ses != nil {
   350  			ses.SetQueryInExecute(false)
   351  		}
   352  	}
   353  }
   354  
   355  // killConnection close the network connection
   356  // myself: true -- the client kill itself.
   357  // myself: false -- the client kill another connection.
   358  func (rt *Routine) killConnection(killMyself bool) {
   359  	//Case 1: kill the connection itself. Do not close the network connection here.
   360  	//label the connection with the cancelled tag
   361  	//if it was cancelled, do nothing
   362  	if rt.setCancelled(true) {
   363  		return
   364  	}
   365  
   366  	//Case 2: kill another connection. Close the network here.
   367  	//    if the connection is processing the request, the response may be dropped.
   368  	//    if the connection is not processing the request, it has no effect.
   369  	if !killMyself {
   370  		//If it is in processing the request, cancel the root context of the connection.
   371  		//At the same time, it cancels all the contexts
   372  		//(includes the request context) derived from the root context.
   373  		//After the context is cancelled. In handleRequest, the network
   374  		//will be closed finally.
   375  		rt.releaseRoutineCtx()
   376  
   377  		//If it is in processing the request, it responds to the client normally
   378  		//before closing the network to avoid the mysql client to be hung.
   379  		closeConn := func() {
   380  			//If it is not in processing the request, just close the network
   381  			proto := rt.protocol
   382  			if proto != nil {
   383  				proto.Quit()
   384  			}
   385  		}
   386  
   387  		rt.execCallbackBasedOnRequest(false, closeConn)
   388  	}
   389  }
   390  
   391  // cleanup When the io is closed, the cleanup will be called in callback Closed().
   392  // cleanup releases the resources only once.
   393  // both the client and the server can close the connection.
   394  func (rt *Routine) cleanup() {
   395  	//step 1: cancel the query if there is a running query.
   396  	//step 2: close the connection.
   397  	rt.closeOnce.Do(func() {
   398  		// we should wait for the migration and close the migration controller.
   399  		rt.mc.waitAndClose()
   400  
   401  		ses := rt.getSession()
   402  		//step A: rollback the txn
   403  		if ses != nil {
   404  			tempExecCtx := ExecCtx{
   405  				ses:    ses,
   406  				txnOpt: FeTxnOption{byRollback: true},
   407  			}
   408  			err := ses.GetTxnHandler().Rollback(&tempExecCtx)
   409  			if err != nil {
   410  				logError(ses, ses.GetDebugString(),
   411  					"Failed to rollback txn",
   412  					zap.Error(err))
   413  			}
   414  		}
   415  
   416  		//step B: cancel the query
   417  		rt.killQuery(false, "")
   418  
   419  		//step C: cancel the root context of the connection.
   420  		//At the same time, it cancels all the contexts
   421  		//(includes the request context) derived from the root context.
   422  		rt.releaseRoutineCtx()
   423  
   424  		//step D: clean protocol
   425  		rt.protocol.Quit()
   426  		rt.protocol = nil
   427  
   428  		//step E: release the resources related to the session
   429  		if ses != nil {
   430  			ses.Close()
   431  			rt.ses = nil
   432  		}
   433  	})
   434  }
   435  
   436  func (rt *Routine) migrateConnectionTo(ctx context.Context, req *query.MigrateConnToRequest) error {
   437  	var err error
   438  	rt.mc.migrateOnce.Do(func() {
   439  		if !rt.mc.beginMigrate() {
   440  			err = moerr.NewInternalErrorNoCtx("cannot start migrate as routine has been closed")
   441  			return
   442  		}
   443  		defer rt.mc.endMigrate()
   444  		ses := rt.getSession()
   445  		err = Migrate(ses, req)
   446  	})
   447  	return err
   448  }
   449  
   450  func (rt *Routine) migrateConnectionFrom(resp *query.MigrateConnFromResponse) error {
   451  	ses := rt.getSession()
   452  	resp.DB = ses.GetDatabaseName()
   453  	for _, st := range ses.GetPrepareStmts() {
   454  		resp.PrepareStmts = append(resp.PrepareStmts, &query.PrepareStmt{
   455  			Name:       st.Name,
   456  			SQL:        st.Sql,
   457  			ParamTypes: st.ParamTypes,
   458  		})
   459  	}
   460  	return nil
   461  }
   462  
   463  func NewRoutine(ctx context.Context, protocol MysqlProtocol, parameters *config.FrontendParameters, rs goetty.IOSession) *Routine {
   464  	ctx = trace.Generate(ctx) // fill span{trace_id} in ctx
   465  	cancelRoutineCtx, cancelRoutineFunc := context.WithCancel(ctx)
   466  	ri := &Routine{
   467  		protocol:          protocol,
   468  		cancelRoutineCtx:  cancelRoutineCtx,
   469  		cancelRoutineFunc: cancelRoutineFunc,
   470  		parameters:        parameters,
   471  		printInfoOnce:     true,
   472  		mc:                newMigrateController(),
   473  	}
   474  	protocol.UpdateCtx(cancelRoutineCtx)
   475  
   476  	return ri
   477  }