golang.org/x/build@v0.0.0-20240506185731-218518f32b70/maintner/subscribe.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package maintner 6 7 import ( 8 "context" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "log" 13 "net/http" 14 "strings" 15 "time" 16 17 "golang.org/x/build/cmd/pubsubhelper/pubsubtypes" 18 ) 19 20 func (c *Corpus) activityChan(topic string) chan struct{} { 21 c.mu.Lock() 22 defer c.mu.Unlock() 23 if ch, ok := c.activityChans[topic]; ok { 24 return ch 25 } 26 if c.activityChans == nil { 27 c.activityChans = map[string]chan struct{}{} 28 } 29 ch := make(chan struct{}) // unbuffered 30 c.activityChans[topic] = ch 31 return ch 32 } 33 34 func (c *Corpus) fire(topic string) { 35 ch := c.activityChan(topic) 36 select { 37 case ch <- struct{}{}: 38 log.Printf("Pubsub woke up sync for topic %q", topic) 39 default: 40 log.Printf("Pubsub event on topic %q discarded; already syncing?", topic) 41 } 42 } 43 44 // StartPubSubHelperSubscribe starts subscribing to a 45 // golang.org/x/build/cmd/pubsubhelper server, such 46 // as https://pubsubhelper.golang.org 47 func (c *Corpus) StartPubSubHelperSubscribe(urlBase string) { 48 go c.subscribeLoop(urlBase) 49 } 50 51 func (c *Corpus) subscribeLoop(urlBase string) { 52 var after time.Time 53 for { 54 newAfter, err := c.getEvent(urlBase, after) 55 if err != nil { 56 log.Printf("pubsub subscribe: %v", err) 57 time.Sleep(5 * time.Second) 58 continue 59 } 60 after = newAfter 61 } 62 } 63 64 var zt time.Time // a zero time.Time 65 66 func (c *Corpus) getEvent(urlBase string, after time.Time) (newAfter time.Time, err error) { 67 var afterStr string 68 if !after.IsZero() { 69 afterStr = after.UTC().Format(time.RFC3339Nano) 70 } 71 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) 72 defer cancel() 73 74 req, _ := http.NewRequest("GET", urlBase+"/waitevent?after="+afterStr, nil) 75 req = req.WithContext(ctx) 76 res, err := http.DefaultClient.Do(req) 77 if err != nil { 78 return zt, err 79 } 80 defer res.Body.Close() 81 if res.StatusCode != 200 { 82 return zt, errors.New(res.Status) 83 } 84 var evt pubsubtypes.Event 85 if err := json.NewDecoder(res.Body).Decode(&evt); err != nil { 86 return zt, err 87 } 88 if !evt.LongPollTimeout { 89 got, _ := json.MarshalIndent(evt, "", "\t") 90 log.Printf("Got pubsubhelper event: %s", got) 91 if gh := evt.GitHub; gh != nil { 92 topic := "github:" + gh.RepoOwner + "/" + gh.Repo 93 c.fire(topic) 94 } 95 if gr := evt.Gerrit; gr != nil { 96 c.fire(gerritTopicOfEvent(gr)) 97 } 98 } 99 return evt.Time.Time(), nil 100 } 101 102 // Return topics like "gerrit:go.googlesource.com/build" 103 func gerritTopicOfEvent(gr *pubsubtypes.GerritEvent) string { 104 server := gr.URL // "https://code-review.googlesource.com/11970 105 if i := strings.Index(server, "//"); i != -1 { 106 server = server[i+2:] // code-review.googlesource.com/11970 107 } 108 if i := strings.Index(server, "/"); i != -1 { 109 server = server[:i] // code-review.googlesource.com 110 } 111 server = strings.Replace(server, "-review.googlesource.com", ".googlesource.com", 1) 112 return fmt.Sprintf("gerrit:%s/%s", server, gr.Project) 113 }