github.com/dolthub/go-mysql-server@v0.18.0/processlist.go (about) 1 // Copyright 2021 Dolthub, Inc. 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 sqle 16 17 import ( 18 "context" 19 "errors" 20 "sync" 21 "time" 22 23 "github.com/sirupsen/logrus" 24 25 "github.com/dolthub/go-mysql-server/sql" 26 ) 27 28 // ProcessList is a structure that keeps track of all the processes and their 29 // status. 30 type ProcessList struct { 31 mu sync.RWMutex 32 procs map[uint32]*sql.Process 33 byQueryPid map[uint64]uint32 34 } 35 36 // NewProcessList creates a new process list. 37 func NewProcessList() *ProcessList { 38 return &ProcessList{ 39 procs: make(map[uint32]*sql.Process), 40 byQueryPid: make(map[uint64]uint32), 41 } 42 } 43 44 // Processes returns the list of current running processes. 45 func (pl *ProcessList) Processes() []sql.Process { 46 pl.mu.RLock() 47 defer pl.mu.RUnlock() 48 var result = make([]sql.Process, 0, len(pl.procs)) 49 50 // Make a deep copy of all maps to avoid race 51 for _, proc := range pl.procs { 52 p := *proc 53 var progMap = make(map[string]sql.TableProgress, len(p.Progress)) 54 for progName, prog := range p.Progress { 55 newProg := sql.TableProgress{ 56 Progress: prog.Progress, 57 PartitionsProgress: make(map[string]sql.PartitionProgress, len(prog.PartitionsProgress)), 58 } 59 for partName, partProg := range prog.PartitionsProgress { 60 newProg.PartitionsProgress[partName] = partProg 61 } 62 progMap[progName] = newProg 63 } 64 p.Progress = progMap 65 result = append(result, p) 66 } 67 68 return result 69 } 70 71 func (pl *ProcessList) AddConnection(id uint32, addr string) { 72 pl.mu.Lock() 73 defer pl.mu.Unlock() 74 pl.procs[id] = &sql.Process{ 75 Connection: id, 76 Command: sql.ProcessCommandConnect, 77 Host: addr, 78 User: "unauthenticated user", 79 StartedAt: time.Now(), 80 } 81 } 82 83 func (pl *ProcessList) ConnectionReady(sess sql.Session) { 84 pl.mu.Lock() 85 defer pl.mu.Unlock() 86 pl.procs[sess.ID()] = &sql.Process{ 87 Connection: sess.ID(), 88 Command: sql.ProcessCommandSleep, 89 Host: sess.Client().Address, 90 User: sess.Client().User, 91 StartedAt: time.Now(), 92 Database: sess.GetCurrentDatabase(), 93 } 94 } 95 96 func (pl *ProcessList) RemoveConnection(connID uint32) { 97 pl.mu.Lock() 98 defer pl.mu.Unlock() 99 p := pl.procs[connID] 100 if p != nil { 101 if p.Kill != nil { 102 p.Kill() 103 } 104 delete(pl.byQueryPid, p.QueryPid) 105 delete(pl.procs, connID) 106 } 107 } 108 109 func (pl *ProcessList) BeginQuery( 110 ctx *sql.Context, 111 query string, 112 ) (*sql.Context, error) { 113 pl.mu.Lock() 114 defer pl.mu.Unlock() 115 id := ctx.Session.ID() 116 pid := ctx.Pid() 117 p := pl.procs[id] 118 if p == nil { 119 return nil, errors.New("internal error: connection not registered with process list") 120 } 121 if _, ok := pl.byQueryPid[pid]; ok { 122 return nil, sql.ErrPidAlreadyUsed.New(pid) 123 } 124 125 newCtx, cancel := context.WithCancel(ctx) 126 ctx = ctx.WithContext(newCtx) 127 128 p.Command = sql.ProcessCommandQuery 129 p.Query = query 130 p.QueryPid = pid 131 p.StartedAt = time.Now() 132 p.Kill = cancel 133 p.Progress = make(map[string]sql.TableProgress) 134 135 pl.byQueryPid[ctx.Pid()] = ctx.Session.ID() 136 137 return ctx, nil 138 } 139 140 func (pl *ProcessList) EndQuery(ctx *sql.Context) { 141 pl.mu.Lock() 142 defer pl.mu.Unlock() 143 id := ctx.Session.ID() 144 pid := ctx.Pid() 145 delete(pl.byQueryPid, pid) 146 p := pl.procs[id] 147 if p != nil && p.QueryPid == pid { 148 p.Command = sql.ProcessCommandSleep 149 p.Query = "" 150 p.StartedAt = time.Now() 151 p.Kill() 152 p.Kill = nil 153 p.QueryPid = 0 154 p.Progress = nil 155 } 156 } 157 158 // UpdateTableProgress updates the progress of the table with the given name for the 159 // process with the given pid. 160 func (pl *ProcessList) UpdateTableProgress(pid uint64, name string, delta int64) { 161 pl.mu.Lock() 162 defer pl.mu.Unlock() 163 164 id, ok := pl.byQueryPid[pid] 165 if !ok { 166 return 167 } 168 p, ok := pl.procs[id] 169 if !ok { 170 return 171 } 172 173 progress, ok := p.Progress[name] 174 if !ok { 175 progress = sql.NewTableProgress(name, -1) 176 } 177 178 progress.Done += delta 179 p.Progress[name] = progress 180 } 181 182 // UpdatePartitionProgress updates the progress of the table partition with the 183 // given name for the process with the given pid. 184 func (pl *ProcessList) UpdatePartitionProgress(pid uint64, tableName, partitionName string, delta int64) { 185 pl.mu.Lock() 186 defer pl.mu.Unlock() 187 188 id, ok := pl.byQueryPid[pid] 189 if !ok { 190 return 191 } 192 p, ok := pl.procs[id] 193 if !ok { 194 return 195 } 196 197 tablePg, ok := p.Progress[tableName] 198 if !ok { 199 return 200 } 201 202 partitionPg, ok := tablePg.PartitionsProgress[partitionName] 203 if !ok { 204 partitionPg = sql.PartitionProgress{Progress: sql.Progress{Name: partitionName, Total: -1}} 205 } 206 207 partitionPg.Done += delta 208 tablePg.PartitionsProgress[partitionName] = partitionPg 209 } 210 211 // AddTableProgress adds a new item to track progress from to the process with 212 // the given pid. If the pid does not exist, it will do nothing. 213 func (pl *ProcessList) AddTableProgress(pid uint64, name string, total int64) { 214 pl.mu.Lock() 215 defer pl.mu.Unlock() 216 217 id, ok := pl.byQueryPid[pid] 218 if !ok { 219 return 220 } 221 p, ok := pl.procs[id] 222 if !ok { 223 return 224 } 225 226 if pg, ok := p.Progress[name]; ok { 227 pg.Total = total 228 p.Progress[name] = pg 229 } else { 230 p.Progress[name] = sql.NewTableProgress(name, total) 231 } 232 } 233 234 // AddPartitionProgress adds a new item to track progress from to the process with 235 // the given pid. If the pid or the table does not exist, it will do nothing. 236 func (pl *ProcessList) AddPartitionProgress(pid uint64, tableName, partitionName string, total int64) { 237 pl.mu.Lock() 238 defer pl.mu.Unlock() 239 240 id, ok := pl.byQueryPid[pid] 241 if !ok { 242 return 243 } 244 p, ok := pl.procs[id] 245 if !ok { 246 return 247 } 248 249 tablePg, ok := p.Progress[tableName] 250 if !ok { 251 return 252 } 253 254 if pg, ok := tablePg.PartitionsProgress[partitionName]; ok { 255 pg.Total = total 256 tablePg.PartitionsProgress[partitionName] = pg 257 } else { 258 tablePg.PartitionsProgress[partitionName] = 259 sql.PartitionProgress{Progress: sql.Progress{Name: partitionName, Total: total}} 260 } 261 } 262 263 // RemoveTableProgress removes an existing item tracking progress from the 264 // process with the given pid, if it exists. 265 func (pl *ProcessList) RemoveTableProgress(pid uint64, name string) { 266 pl.mu.Lock() 267 defer pl.mu.Unlock() 268 269 id, ok := pl.byQueryPid[pid] 270 if !ok { 271 return 272 } 273 p, ok := pl.procs[id] 274 if !ok { 275 return 276 } 277 278 delete(p.Progress, name) 279 } 280 281 // RemovePartitionProgress removes an existing item tracking progress from the 282 // process with the given pid, if it exists. 283 func (pl *ProcessList) RemovePartitionProgress(pid uint64, tableName, partitionName string) { 284 pl.mu.Lock() 285 defer pl.mu.Unlock() 286 287 id, ok := pl.byQueryPid[pid] 288 if !ok { 289 return 290 } 291 p, ok := pl.procs[id] 292 if !ok { 293 return 294 } 295 296 tablePg, ok := p.Progress[tableName] 297 if !ok { 298 return 299 } 300 301 delete(tablePg.PartitionsProgress, partitionName) 302 } 303 304 // Kill terminates all queries for a given connection id. 305 func (pl *ProcessList) Kill(connID uint32) { 306 pl.mu.Lock() 307 defer pl.mu.Unlock() 308 309 p := pl.procs[connID] 310 if p != nil && p.Kill != nil { 311 logrus.Infof("kill query: pid %d", p.QueryPid) 312 p.Kill() 313 } 314 }