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  }