google.golang.org/grpc@v1.62.1/internal/grpcsync/callback_serializer_test.go (about)

     1  /*
     2   *
     3   * Copyright 2022 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package grpcsync
    20  
    21  import (
    22  	"context"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  )
    29  
    30  const (
    31  	defaultTestTimeout      = 5 * time.Second
    32  	defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen.
    33  )
    34  
    35  // TestCallbackSerializer_Schedule_FIFO verifies that callbacks are executed in
    36  // the same order in which they were scheduled.
    37  func (s) TestCallbackSerializer_Schedule_FIFO(t *testing.T) {
    38  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
    39  	cs := NewCallbackSerializer(ctx)
    40  	defer cancel()
    41  
    42  	// We have two channels, one to record the order of scheduling, and the
    43  	// other to record the order of execution. We spawn a bunch of goroutines
    44  	// which record the order of scheduling and call the actual Schedule()
    45  	// method as well.  The callbacks record the order of execution.
    46  	//
    47  	// We need to grab a lock to record order of scheduling to guarantee that
    48  	// the act of recording and the act of calling Schedule() happen atomically.
    49  	const numCallbacks = 100
    50  	var mu sync.Mutex
    51  	scheduleOrderCh := make(chan int, numCallbacks)
    52  	executionOrderCh := make(chan int, numCallbacks)
    53  	for i := 0; i < numCallbacks; i++ {
    54  		go func(id int) {
    55  			mu.Lock()
    56  			defer mu.Unlock()
    57  			scheduleOrderCh <- id
    58  			cs.Schedule(func(ctx context.Context) {
    59  				select {
    60  				case <-ctx.Done():
    61  					return
    62  				case executionOrderCh <- id:
    63  				}
    64  			})
    65  		}(i)
    66  	}
    67  
    68  	// Spawn a couple of goroutines to capture the order or scheduling and the
    69  	// order of execution.
    70  	scheduleOrder := make([]int, numCallbacks)
    71  	executionOrder := make([]int, numCallbacks)
    72  	var wg sync.WaitGroup
    73  	wg.Add(2)
    74  	go func() {
    75  		defer wg.Done()
    76  		for i := 0; i < numCallbacks; i++ {
    77  			select {
    78  			case <-ctx.Done():
    79  				return
    80  			case id := <-scheduleOrderCh:
    81  				scheduleOrder[i] = id
    82  			}
    83  		}
    84  	}()
    85  	go func() {
    86  		defer wg.Done()
    87  		for i := 0; i < numCallbacks; i++ {
    88  			select {
    89  			case <-ctx.Done():
    90  				return
    91  			case id := <-executionOrderCh:
    92  				executionOrder[i] = id
    93  			}
    94  		}
    95  	}()
    96  	wg.Wait()
    97  
    98  	if diff := cmp.Diff(executionOrder, scheduleOrder); diff != "" {
    99  		t.Fatalf("Callbacks are not executed in scheduled order. diff(-want, +got):\n%s", diff)
   100  	}
   101  }
   102  
   103  // TestCallbackSerializer_Schedule_Concurrent verifies that all concurrently
   104  // scheduled callbacks get executed.
   105  func (s) TestCallbackSerializer_Schedule_Concurrent(t *testing.T) {
   106  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   107  	cs := NewCallbackSerializer(ctx)
   108  	defer cancel()
   109  
   110  	// Schedule callbacks concurrently by calling Schedule() from goroutines.
   111  	// The execution of the callbacks call Done() on the waitgroup, which
   112  	// eventually unblocks the test and allows it to complete.
   113  	const numCallbacks = 100
   114  	var wg sync.WaitGroup
   115  	wg.Add(numCallbacks)
   116  	for i := 0; i < numCallbacks; i++ {
   117  		go func() {
   118  			cs.Schedule(func(context.Context) {
   119  				wg.Done()
   120  			})
   121  		}()
   122  	}
   123  
   124  	// We call Wait() on the waitgroup from a goroutine so that we can select on
   125  	// the Wait() being unblocked and the overall test deadline expiring.
   126  	done := make(chan struct{})
   127  	go func() {
   128  		wg.Wait()
   129  		close(done)
   130  	}()
   131  
   132  	select {
   133  	case <-ctx.Done():
   134  		t.Fatal("Timeout waiting for all scheduled callbacks to be executed")
   135  	case <-done:
   136  	}
   137  }
   138  
   139  // TestCallbackSerializer_Schedule_Close verifies that callbacks in the queue
   140  // are not executed once Close() returns.
   141  func (s) TestCallbackSerializer_Schedule_Close(t *testing.T) {
   142  	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   143  	defer cancel()
   144  
   145  	serializerCtx, serializerCancel := context.WithTimeout(context.Background(), defaultTestTimeout)
   146  	cs := NewCallbackSerializer(serializerCtx)
   147  
   148  	// Schedule a callback which blocks until the context passed to it is
   149  	// canceled. It also closes a channel to signal that it has started.
   150  	firstCallbackStartedCh := make(chan struct{})
   151  	cs.Schedule(func(ctx context.Context) {
   152  		close(firstCallbackStartedCh)
   153  		<-ctx.Done()
   154  	})
   155  
   156  	// Schedule a bunch of callbacks. These should be exeuted since the are
   157  	// scheduled before the serializer is closed.
   158  	const numCallbacks = 10
   159  	callbackCh := make(chan int, numCallbacks)
   160  	for i := 0; i < numCallbacks; i++ {
   161  		num := i
   162  		if !cs.Schedule(func(context.Context) { callbackCh <- num }) {
   163  			t.Fatal("Schedule failed to accept a callback when the serializer is yet to be closed")
   164  		}
   165  	}
   166  
   167  	// Ensure that none of the newer callbacks are executed at this point.
   168  	select {
   169  	case <-time.After(defaultTestShortTimeout):
   170  	case <-callbackCh:
   171  		t.Fatal("Newer callback executed when older one is still executing")
   172  	}
   173  
   174  	// Wait for the first callback to start before closing the scheduler.
   175  	<-firstCallbackStartedCh
   176  
   177  	// Cancel the context which will unblock the first callback. All of the
   178  	// other callbacks (which have not started executing at this point) should
   179  	// be executed after this.
   180  	serializerCancel()
   181  
   182  	// Ensure that the newer callbacks are executed.
   183  	for i := 0; i < numCallbacks; i++ {
   184  		select {
   185  		case <-ctx.Done():
   186  			t.Fatal("Timeout when waiting for callback scheduled before close to be executed")
   187  		case num := <-callbackCh:
   188  			if num != i {
   189  				t.Fatalf("Executing callback %d, want %d", num, i)
   190  			}
   191  		}
   192  	}
   193  	<-cs.Done()
   194  
   195  	done := make(chan struct{})
   196  	if cs.Schedule(func(context.Context) { close(done) }) {
   197  		t.Fatal("Scheduled a callback after closing the serializer")
   198  	}
   199  
   200  	// Ensure that the lates callback is executed at this point.
   201  	select {
   202  	case <-time.After(defaultTestShortTimeout):
   203  	case <-done:
   204  		t.Fatal("Newer callback executed when scheduled after closing serializer")
   205  	}
   206  }