kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/graphstore/proxy/proxy.go (about)

     1  /*
     2   * Copyright 2015 The Kythe Authors. All rights reserved.
     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 proxy defines a proxy graphstore.Service that delegates requests to
    18  // other service implementations.
    19  package proxy // import "kythe.io/kythe/go/services/graphstore/proxy"
    20  
    21  import (
    22  	"container/heap"
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"strings"
    28  	"sync"
    29  
    30  	"kythe.io/kythe/go/services/graphstore"
    31  	"kythe.io/kythe/go/storage/gsutil"
    32  	"kythe.io/kythe/go/util/compare"
    33  
    34  	spb "kythe.io/kythe/proto/storage_go_proto"
    35  )
    36  
    37  func init() {
    38  	gsutil.Register("proxy", proxyHandler)
    39  }
    40  
    41  func proxyHandler(spec string) (graphstore.Service, error) {
    42  	var stores []graphstore.Service
    43  	for _, s := range strings.Split(spec, ",") {
    44  		gs, err := gsutil.ParseGraphStore(s)
    45  		if err != nil {
    46  			return nil, fmt.Errorf("proxy GraphStore error for %q: %v", s, err)
    47  		}
    48  		stores = append(stores, gs)
    49  	}
    50  	if len(stores) == 0 {
    51  		return nil, errors.New("no proxy GraphStores specified")
    52  	}
    53  	return New(stores...), nil
    54  }
    55  
    56  type proxyService struct {
    57  	stores []graphstore.Service
    58  }
    59  
    60  // New returns a graphstore.Service that forwards Reads, Writes, and Scans to a
    61  // set of stores in parallel, and merges their results.
    62  func New(stores ...graphstore.Service) graphstore.Service { return &proxyService{stores} }
    63  
    64  // Read implements graphstore.Service and forwards the request to the proxied stores.
    65  func (p *proxyService) Read(ctx context.Context, req *spb.ReadRequest, f graphstore.EntryFunc) error {
    66  	return p.invoke(func(svc graphstore.Service, cb graphstore.EntryFunc) error {
    67  		return svc.Read(ctx, req, cb)
    68  	}, f)
    69  }
    70  
    71  // Scan implements part of graphstore.Service by forwarding the request to the
    72  // proxied stores.
    73  func (p *proxyService) Scan(ctx context.Context, req *spb.ScanRequest, f graphstore.EntryFunc) error {
    74  	return p.invoke(func(svc graphstore.Service, cb graphstore.EntryFunc) error {
    75  		return svc.Scan(ctx, req, cb)
    76  	}, f)
    77  }
    78  
    79  // Write implements part of graphstore.Service by forwarding the request to the
    80  // proxied stores.
    81  func (p *proxyService) Write(ctx context.Context, req *spb.WriteRequest) error {
    82  	return waitErr(p.foreach(func(i int, s graphstore.Service) error {
    83  		return s.Write(ctx, req)
    84  	}))
    85  }
    86  
    87  // Close implements part of graphstore.Service by calling Close on each proxied
    88  // store.  All the stores are given an opportunity to close, even in case of
    89  // error, but only one error is returned.
    90  func (p *proxyService) Close(ctx context.Context) error {
    91  	return waitErr(p.foreach(func(i int, s graphstore.Service) error {
    92  		return s.Close(ctx)
    93  	}))
    94  }
    95  
    96  // waitErr reads values from errc until it closes, then returns the first
    97  // non-nil error it received (if any).
    98  func waitErr(errc <-chan error) error {
    99  	var err error
   100  	for e := range errc {
   101  		if e != nil && err == nil {
   102  			err = e
   103  		}
   104  	}
   105  	return err
   106  }
   107  
   108  // foreach concurrently invokes f(i, p.stores[i]) for each proxied store.  The
   109  // return value from each invocation is delivered to the error channel that is
   110  // returned, which will be closed once all the calls are complete.  The channel
   111  // is unbuffered, so the caller must drain the channel to avoid deadlock.
   112  func (p *proxyService) foreach(f func(int, graphstore.Service) error) <-chan error {
   113  	errc := make(chan error)
   114  	var wg sync.WaitGroup
   115  	wg.Add(len(p.stores))
   116  	for i, s := range p.stores {
   117  		i, s := i, s
   118  		go func() {
   119  			defer wg.Done()
   120  			errc <- f(i, s)
   121  		}()
   122  	}
   123  	go func() { wg.Wait(); close(errc) }()
   124  	return errc
   125  }
   126  
   127  // invoke calls req concurrently for each delegated service in p, merges the
   128  // results, and delivers them to f.
   129  func (p *proxyService) invoke(req func(graphstore.Service, graphstore.EntryFunc) error, f graphstore.EntryFunc) error {
   130  	stop := make(chan struct{}) // Closed to signal cancellation
   131  
   132  	// Create a channel for each delegated request, and a callback that
   133  	// delivers results to that channel.  The callback will handle cancellation
   134  	// signaled by a close of the stop channel, and exit early.
   135  
   136  	rcv := make([]graphstore.EntryFunc, len(p.stores)) // callbacks
   137  	chs := make([]chan *spb.Entry, len(p.stores))      // channels
   138  	for i := range p.stores {
   139  		ch := make(chan *spb.Entry)
   140  		chs[i] = ch
   141  		rcv[i] = func(e *spb.Entry) error {
   142  			select {
   143  			case <-stop: // cancellation has been signalled
   144  				return nil
   145  			case ch <- e:
   146  				return nil
   147  			}
   148  		}
   149  	}
   150  
   151  	// Invoke the requests for each service, using the corresponding callback.
   152  	errc := p.foreach(func(i int, s graphstore.Service) error {
   153  		err := req(s, rcv[i])
   154  		close(chs[i])
   155  		return err
   156  	})
   157  
   158  	// Accumulate and merge the results.  This is a straightforward round-robin
   159  	// n-finger merge of the values from the delegated requests.
   160  
   161  	var h compare.ByEntries // used to preserve stream order
   162  	var last *spb.Entry     // used to deduplicate entries
   163  	var perr error          // error while accumulating
   164  	go func() {
   165  		defer close(stop)
   166  		for {
   167  			hit := false // are any requests still pending?
   168  
   169  			// Give each channel a chance to produce a value, round-robin to
   170  			// preserve the global ordering.
   171  			for _, ch := range chs {
   172  				if e, ok := <-ch; ok {
   173  					hit = true
   174  					heap.Push(&h, e)
   175  				}
   176  			}
   177  
   178  			// If there are any values pending, deliver one to the consumer.
   179  			// If not, and there are no more values coming, we're finished.
   180  			if h.Len() != 0 {
   181  				entry := heap.Pop(&h).(*spb.Entry)
   182  				if last == nil || !compare.EntriesEqual(last, entry) {
   183  					last = entry
   184  					if err := f(entry); err != nil {
   185  						if err != io.EOF {
   186  							perr = err
   187  						}
   188  						return
   189  					}
   190  				}
   191  			} else if !hit {
   192  				return // no more work to do
   193  			}
   194  		}
   195  	}()
   196  	err := waitErr(errc) // wait for all receives to complete
   197  	<-stop               // wait for all sends to complete
   198  	if perr != nil {
   199  		return perr
   200  	}
   201  	return err
   202  }