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  }