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 }