github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/index/async_indexed_lookups.go (about) 1 // Copyright 2020 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 index 16 17 import ( 18 "context" 19 "fmt" 20 "runtime" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 24 "github.com/dolthub/dolt/go/libraries/utils/async" 25 "github.com/dolthub/dolt/go/store/types" 26 ) 27 28 type lookupResult struct { 29 idx uint64 30 r sql.Row 31 err error 32 } 33 34 // toLookup represents an table lookup that should be performed by one of the global asyncLookups instance's worker routines 35 type toLookup struct { 36 idx uint64 37 t types.Tuple 38 tupleToRow func(context.Context, types.Tuple) (sql.Row, error) 39 resBuf *async.RingBuffer 40 epoch int 41 ctx context.Context 42 } 43 44 // asyncLookups is a pool of worker routines reading from a channel doing table lookups 45 type asyncLookups struct { 46 ctx context.Context 47 toLookupCh chan toLookup 48 } 49 50 // newAsyncLookups kicks off a number of worker routines and creates a channel for sending lookups to workers. The 51 // routines live for the life of the program 52 func newAsyncLookups(bufferSize int) *asyncLookups { 53 toLookupCh := make(chan toLookup, bufferSize) 54 art := &asyncLookups{toLookupCh: toLookupCh} 55 56 return art 57 } 58 59 func (art *asyncLookups) start(numWorkers int) { 60 for i := 0; i < numWorkers; i++ { 61 go func() { 62 art.workerFunc() 63 }() 64 } 65 } 66 67 func (art *asyncLookups) workerFunc() { 68 f := func() { 69 var curr toLookup 70 var ok bool 71 72 defer func() { 73 if r := recover(); r != nil { 74 // Attempt to write a failure to the channel and discard any additional errors 75 if err, ok := r.(error); ok { 76 _ = curr.resBuf.Push(lookupResult{idx: curr.idx, r: nil, err: err}, curr.epoch) 77 } else { 78 _ = curr.resBuf.Push(lookupResult{idx: curr.idx, r: nil, err: fmt.Errorf("%v", r)}, curr.epoch) 79 } 80 } 81 82 // if the channel used for lookups is closed then fail spectacularly 83 if !ok { 84 panic("toLookup channel closed. All lookups will fail and will result in a deadlock") 85 } 86 }() 87 88 for { 89 curr, ok = <-art.toLookupCh 90 91 if !ok { 92 break 93 } 94 95 r, err := curr.tupleToRow(curr.ctx, curr.t) 96 _ = curr.resBuf.Push(lookupResult{idx: curr.idx, r: r, err: err}, curr.epoch) 97 } 98 } 99 100 // these routines will run forever unless f is allowed to panic which only happens when the lookup channel is closed 101 for { 102 f() 103 } 104 } 105 106 // lookups is a global asyncLookups instance which is used by the indexLookupRowIterAdapter 107 var lookups *asyncLookups 108 109 func init() { 110 lookups = newAsyncLookups(runtime.NumCPU() * 256) 111 lookups.start(runtime.NumCPU()) 112 }