@@ -34,6 +34,7 @@ import (
34
34
"github.com/pingcap/tidb/util/logutil"
35
35
"github.com/pingcap/tidb/util/sqlexec"
36
36
"go.uber.org/zap"
37
+ "golang.org/x/sync/singleflight"
37
38
)
38
39
39
40
type statsWrapper struct {
@@ -47,7 +48,7 @@ type StatsLoad struct {
47
48
SubCtxs []sessionctx.Context
48
49
NeededItemsCh chan * NeededItemTask
49
50
TimeoutItemsCh chan * NeededItemTask
50
- WorkingColMap map [model. TableItemID ][] chan stmtctx. StatsLoadResult
51
+ Singleflight singleflight. Group
51
52
}
52
53
53
54
// NeededItemTask represents one needed column/indices with expire time.
@@ -235,49 +236,63 @@ func (h *Handle) HandleOneTask(sctx sessionctx.Context, lastTask *NeededItemTask
235
236
} else {
236
237
task = lastTask
237
238
}
238
- return h .handleOneItemTask (task , readerCtx , ctx )
239
+ resultChan := h .StatsLoad .Singleflight .DoChan (task .TableItemID .Key (), func () (any , error ) {
240
+ return h .handleOneItemTask (task , readerCtx , ctx )
241
+ })
242
+ timeout := time .Until (task .ToTimeout )
243
+ select {
244
+ case result := <- resultChan :
245
+ if result .Err == nil {
246
+ slr := result .Val .(* stmtctx.StatsLoadResult )
247
+ if slr .Error != nil {
248
+ return task , slr .Error
249
+ }
250
+ task .ResultCh <- * slr
251
+ return nil , nil
252
+ }
253
+ return task , result .Err
254
+ case <- time .After (timeout ):
255
+ return task , nil
256
+ }
239
257
}
240
258
241
- func (h * Handle ) handleOneItemTask (task * NeededItemTask , readerCtx * StatsReaderContext , ctx sqlexec.RestrictedSQLExecutor ) (* NeededItemTask , error ) {
242
- result := stmtctx.StatsLoadResult {Item : task .TableItemID }
259
+ func (h * Handle ) handleOneItemTask (task * NeededItemTask , readerCtx * StatsReaderContext , ctx sqlexec.RestrictedSQLExecutor ) (result * stmtctx.StatsLoadResult , err error ) {
260
+ defer func () {
261
+ // recover for each task, worker keeps working
262
+ if r := recover (); r != nil {
263
+ logutil .BgLogger ().Error ("handleOneItemTask panicked" , zap .Any ("recover" , r ), zap .Stack ("stack" ))
264
+ err = errors .Errorf ("stats loading panicked: %v" , r )
265
+ }
266
+ }()
267
+ result = & stmtctx.StatsLoadResult {Item : task .TableItemID }
243
268
item := result .Item
244
269
oldCache := h .statsCache .Load ().(statsCache )
245
270
tbl , ok := oldCache .Get (item .TableID )
246
271
if ! ok {
247
- h .writeToResultChan (task .ResultCh , result )
248
- return nil , nil
272
+ return result , nil
249
273
}
250
- var err error
251
274
wrapper := & statsWrapper {}
252
275
if item .IsIndex {
253
276
index , ok := tbl .Indices [item .ID ]
254
277
if ! ok || index .IsFullLoad () {
255
- h .writeToResultChan (task .ResultCh , result )
256
- return nil , nil
278
+ return result , nil
257
279
}
258
280
wrapper .idx = index
259
281
} else {
260
282
col , ok := tbl .Columns [item .ID ]
261
283
if ! ok || col .IsFullLoad () {
262
- h .writeToResultChan (task .ResultCh , result )
263
- return nil , nil
284
+ return result , nil
264
285
}
265
286
wrapper .col = col
266
287
}
267
- // to avoid duplicated handling in concurrent scenario
268
- working := h .setWorking (result .Item , task .ResultCh )
269
- if ! working {
270
- h .writeToResultChan (task .ResultCh , result )
271
- return nil , nil
272
- }
273
288
// refresh statsReader to get latest stats
274
289
h .loadFreshStatsReader (readerCtx , ctx )
275
290
t := time .Now ()
276
291
needUpdate := false
277
292
wrapper , err = h .readStatsForOneItem (item , wrapper , readerCtx .reader )
278
293
if err != nil {
279
294
result .Error = err
280
- return task , err
295
+ return result , err
281
296
}
282
297
if item .IsIndex {
283
298
if wrapper .idx != nil {
@@ -290,9 +305,8 @@ func (h *Handle) handleOneItemTask(task *NeededItemTask, readerCtx *StatsReaderC
290
305
}
291
306
metrics .ReadStatsHistogram .Observe (float64 (time .Since (t ).Milliseconds ()))
292
307
if needUpdate && h .updateCachedItem (item , wrapper .col , wrapper .idx ) {
293
- h . writeToResultChan ( task . ResultCh , result )
308
+ return result , nil
294
309
}
295
- h .finishWorking (result )
296
310
return nil , nil
297
311
}
298
312
@@ -509,32 +523,3 @@ func (h *Handle) updateCachedItem(item model.TableItemID, colHist *statistics.Co
509
523
}
510
524
return h .updateStatsCache (oldCache .update ([]* statistics.Table {tbl }, nil , oldCache .version , WithTableStatsByQuery ()))
511
525
}
512
-
513
- func (h * Handle ) setWorking (item model.TableItemID , resultCh chan stmtctx.StatsLoadResult ) bool {
514
- h .StatsLoad .Lock ()
515
- defer h .StatsLoad .Unlock ()
516
- chList , ok := h .StatsLoad .WorkingColMap [item ]
517
- if ok {
518
- if chList [0 ] == resultCh {
519
- return true // just return for duplicate setWorking
520
- }
521
- h .StatsLoad .WorkingColMap [item ] = append (chList , resultCh )
522
- return false
523
- }
524
- chList = []chan stmtctx.StatsLoadResult {}
525
- chList = append (chList , resultCh )
526
- h .StatsLoad .WorkingColMap [item ] = chList
527
- return true
528
- }
529
-
530
- func (h * Handle ) finishWorking (result stmtctx.StatsLoadResult ) {
531
- h .StatsLoad .Lock ()
532
- defer h .StatsLoad .Unlock ()
533
- if chList , ok := h .StatsLoad .WorkingColMap [result .Item ]; ok {
534
- list := chList [1 :]
535
- for _ , ch := range list {
536
- h .writeToResultChan (ch , result )
537
- }
538
- }
539
- delete (h .StatsLoad .WorkingColMap , result .Item )
540
- }
0 commit comments