github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/remotestorage/internal/pool/pool.go (about)

     1  // Copyright 2024 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 pool
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"sync"
    21  
    22  	"golang.org/x/sync/errgroup"
    23  )
    24  
    25  type Func func(ctx context.Context, shutdown <-chan struct{}) error
    26  
    27  // A |pool.Dynamic| is a thread pool which can be dynamically sized from the
    28  // outside. It is created with a |Func|, which represents the thread function
    29  // to run on each worker thread. That |Func| must adhere to a contract where if
    30  // it reads from the supplied |shutdown| channel, it shutsdown in a timely
    31  // manner.
    32  //
    33  // To run the threads in the pool, call |Run|.
    34  //
    35  // To set the desired size of the pool, call |SetSize|. If |size| is larger
    36  // than the current size, new threads will be created immediately to bring the
    37  // size up to the requested size. If |size| is smaller than the requested size,
    38  // then |curSize-size| sends on |shutdown| will be performed, to bring
    39  // |curSize| down to |size|. |SetSize| does not return until the pool is
    40  // resized or the pool's |Run| method has errored.
    41  //
    42  // |Func|s are run within an |errgroup|. If any |Func| exists with a non-|nil|
    43  // error, |Run| will exit with an error as well.
    44  type Dynamic struct {
    45  	// errgroup Context in which all spawned |Func|s are run.
    46  	ctx context.Context
    47  	// errgroup in which all spawned |Func|s are run.
    48  	eg *errgroup.Group
    49  
    50  	// Guards |size| and |running|.
    51  	mu sync.Mutex
    52  
    53  	// |SetSize| does not adjust the thread pool unless the pool is already running.
    54  	running bool
    55  
    56  	// The requested size of the pool. After the pool is running, this is
    57  	// also the current size of the pool.
    58  	size int
    59  
    60  	// The worker function each thread in the pool runs.
    61  	f Func
    62  
    63  	// Sends are made on this channel to request a spawn.
    64  	spawnCh chan struct{}
    65  
    66  	// Sends are made on this channel to request a thread to shutdown. This
    67  	// channel is supplied to |Func|s and they are responsible for adhering
    68  	// to the request.
    69  	shutdownCh chan struct{}
    70  
    71  	// Sends are made on this channel when the thread successfully exits.
    72  	// When decreasing the number of workers, delivers on |shutdownCh| must
    73  	// be paired up with receives on |exitCh|.
    74  	exitCh chan struct{}
    75  
    76  	// Closed when it is time for the pool to shutdown cleanly. Used by |Close|.
    77  	poolShutdownCh chan struct{}
    78  }
    79  
    80  func NewDynamic(ctx context.Context, f Func, size int) *Dynamic {
    81  	if size == 0 {
    82  		panic("cannot create pool of initial size 0")
    83  	}
    84  	if f == nil {
    85  		panic("cannot create pool with nil Func")
    86  	}
    87  	eg, ctx := errgroup.WithContext(ctx)
    88  	return &Dynamic{
    89  		ctx:  ctx,
    90  		eg:   eg,
    91  		size: size,
    92  		f:    f,
    93  
    94  		spawnCh:    make(chan struct{}),
    95  		shutdownCh: make(chan struct{}),
    96  		exitCh:     make(chan struct{}),
    97  
    98  		poolShutdownCh: make(chan struct{}),
    99  	}
   100  }
   101  
   102  func (d *Dynamic) Run() error {
   103  	d.mu.Lock()
   104  	if d.running {
   105  		d.mu.Unlock()
   106  		return errors.New("internal error: Dynamic Run() was called on a pool which has already been run or is currently running.")
   107  	}
   108  	d.running = true
   109  	d.eg.Go(func() error {
   110  		for {
   111  			select {
   112  			case <-d.spawnCh:
   113  				d.eg.Go(func() (err error) {
   114  					defer func() {
   115  						if err != nil {
   116  							return
   117  						}
   118  						select {
   119  						case d.exitCh <- struct{}{}:
   120  						case <-d.ctx.Done():
   121  						}
   122  					}()
   123  					return d.f(d.ctx, d.shutdownCh)
   124  				})
   125  			case <-d.ctx.Done():
   126  				return context.Cause(d.ctx)
   127  			case <-d.poolShutdownCh:
   128  				return nil
   129  			}
   130  		}
   131  	})
   132  	for i := 0; i < d.size; i++ {
   133  		select {
   134  		case d.spawnCh <- struct{}{}:
   135  		case <-d.ctx.Done():
   136  			d.mu.Unlock()
   137  			return d.eg.Wait()
   138  		}
   139  	}
   140  	d.mu.Unlock()
   141  	return d.eg.Wait()
   142  }
   143  
   144  func (d *Dynamic) Close() {
   145  	d.SetSize(0)
   146  	close(d.poolShutdownCh)
   147  }
   148  
   149  func (d *Dynamic) SetSize(n int) {
   150  	d.mu.Lock()
   151  	defer d.mu.Unlock()
   152  	if d.size == 0 {
   153  		// Ignore the request; this pool is shutdown.
   154  		return
   155  	}
   156  	// We are spawning new threads.
   157  	for d.size < n {
   158  		select {
   159  		case d.spawnCh <- struct{}{}:
   160  		case <-d.ctx.Done():
   161  			return
   162  		}
   163  		d.size += 1
   164  	}
   165  	// We are shutting down existing threads.
   166  	for d.size > n {
   167  		select {
   168  		case d.shutdownCh <- struct{}{}:
   169  		case <-d.ctx.Done():
   170  			return
   171  		}
   172  		select {
   173  		case <-d.exitCh:
   174  		case <-d.ctx.Done():
   175  			return
   176  		}
   177  		d.size -= 1
   178  	}
   179  }