code: func (w *indexWorker) fetchHandles(ctx context.Context, result distsql.SelectResult) (count int64, err error) {
  	defer func() {
  		if r := recover(); r != nil {
  			buf := make([]byte, 4096)
  			stackSize := runtime.Stack(buf, false)
  			buf = buf[:stackSize]
  			logutil.Logger(ctx).Error("indexWorker in IndexLookupExecutor panicked", zap.String("stack", string(buf)))
  			err4Panic := errors.Errorf("%v", r)
  			doneCh := make(chan error, 1)
  			doneCh <- err4Panic
  			w.resultCh <- &lookupTableTask{
  				doneCh: doneCh,
  			}
  			if err != nil {
  				err = errors.Trace(err4Panic)
  			}
  		}
  	}()
  	chk := chunk.NewChunkWithCapacity([]*types.FieldType{types.NewFieldType(mysql.TypeLonglong)}, w.idxLookup.maxChunkSize)
  	for {
  		handles, err := w.extractTaskHandles(ctx, chk, result)
  		if err != nil {
  			doneCh := make(chan error, 1)
  			doneCh <- err
  			w.resultCh <- &lookupTableTask{
  				doneCh: doneCh,
  			}
  			return count, err
  		}
  		if len(handles) == 0 {
  			return count, nil
  		}
  		count += int64(len(handles))
  		task := w.buildTableTask(handles)
  		select {
  		case <-ctx.Done():
  			return count, nil
  		case <-w.finished:
  			return count, nil
  		case w.workCh <- task:
  			w.resultCh <- task
  		}
  	}
  }
docs: // fetchHandles fetches a batch of handles from index data and builds the index lookup tasks.
  // The tasks are sent to workCh to be further processed by tableWorker, and sent to e.resultCh
  // at the same time to keep data ordered.