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 }