vitess.io/vitess@v0.16.2/go/vt/vtgate/buffer/timeout_thread.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package buffer 18 19 import ( 20 "sync" 21 "time" 22 ) 23 24 // timeoutThread captures the state of the timeout thread. 25 // The thread actively removes the head of the queue when that entry exceeds 26 // its buffering window. 27 // For each active failover there will be one thread (Go routine). 28 type timeoutThread struct { 29 sb *shardBuffer 30 // maxDuration enforces that a failover stops after 31 // -buffer_max_failover_duration at most. 32 maxDuration *time.Timer 33 // stopChan will be closed when the thread should stop e.g. before the drain. 34 stopChan chan struct{} 35 wg sync.WaitGroup 36 37 // mu guards access to "queueNotEmpty" between this thread and callers of 38 // notifyQueueNotEmpty(). 39 mu sync.Mutex 40 // queueNotEmpty will be closed to notify the timeout thread when the queue 41 // state changes from empty to non-empty. After it's closed, a new object will 42 // be assigned to this field. 43 queueNotEmpty chan struct{} 44 } 45 46 func newTimeoutThread(sb *shardBuffer, maxFailoverDuration time.Duration) *timeoutThread { 47 return &timeoutThread{ 48 sb: sb, 49 maxDuration: time.NewTimer(maxFailoverDuration), 50 stopChan: make(chan struct{}), 51 queueNotEmpty: make(chan struct{}), 52 } 53 } 54 55 func (tt *timeoutThread) start() { 56 tt.wg.Add(1) 57 go tt.run() 58 } 59 60 // stop stops the thread and blocks until it has exited. 61 func (tt *timeoutThread) stop() { 62 close(tt.stopChan) 63 tt.wg.Wait() 64 } 65 66 func (tt *timeoutThread) notifyQueueNotEmpty() { 67 tt.mu.Lock() 68 defer tt.mu.Unlock() 69 70 close(tt.queueNotEmpty) 71 // Create a new channel which will be used by the next notify call. 72 tt.queueNotEmpty = make(chan struct{}) 73 } 74 75 func (tt *timeoutThread) run() { 76 defer tt.wg.Done() 77 defer tt.maxDuration.Stop() 78 79 // While this thread is running, it can be in two states: 80 for { 81 if e := tt.sb.oldestEntry(); e != nil { 82 // 1. queue not empty: Wait for the oldest entry to exceed the window. 83 if stopped := tt.waitForEntry(e); stopped { 84 return 85 } 86 } else { 87 // 2. queue empty: Wait for an entry to show up. 88 if stopped := tt.waitForNonEmptyQueue(); stopped { 89 return 90 } 91 } 92 } 93 } 94 95 // waitForEntry blocks until "e" exceeds its buffering window or buffering stops 96 // in general. It returns true if the timeout thread should stop. 97 func (tt *timeoutThread) waitForEntry(e *entry) bool { 98 windowExceeded := time.NewTimer(time.Until(e.deadline)) 99 defer windowExceeded.Stop() 100 101 select { 102 // a) Always check these channels, regardless of the state. 103 case <-tt.maxDuration.C: 104 // Max duration is up. Stop buffering. Do not error out entries explicitly. 105 tt.sb.stopBufferingDueToMaxDuration() 106 return true 107 case <-tt.stopChan: 108 // Failover ended before timeout. Do nothing. 109 return true 110 // b) Entry-specific checks. 111 case <-e.done: 112 // Entry was drained or evicted. Get the next entry. 113 return false 114 // NOTE: We're not waiting for e.bufferCtx here (which triggers when the 115 // request was externally aborted e.g. due to context canceled) because then 116 // this thread would race with the request thread which runs 117 // shardBuffer.remove(). Instead, remove() will notify us here eventually by 118 // closing "e.done". 119 case <-windowExceeded.C: 120 // Entry expired. Evict it and then get the next entry. 121 tt.sb.evictOldestEntry(e) 122 return false 123 } 124 } 125 126 // waitForNonEmptyQueue blocks until the buffer queue gets a new element or 127 // the timeout thread should be stopped. 128 // It returns true if the timeout thread should stop. 129 func (tt *timeoutThread) waitForNonEmptyQueue() bool { 130 tt.mu.Lock() 131 queueNotEmpty := tt.queueNotEmpty 132 tt.mu.Unlock() 133 134 select { 135 // a) Always check these channels, regardless of the state. 136 case <-tt.maxDuration.C: 137 // Max duration is up. Stop buffering. Do not error out entries explicitly. 138 tt.sb.stopBufferingDueToMaxDuration() 139 return true 140 case <-tt.stopChan: 141 // Failover ended before timeout. Do nothing. 142 return true 143 // b) State-specific check. 144 case <-queueNotEmpty: 145 // At least one entry present. Check its timeout in the next iteration. 146 return false 147 } 148 }