github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/talks/2013/advconc/realmain/realmain.go (about)

     1  // +build OMIT
     2  
     3  // realmain runs the Subscribe example with a real RSS fetcher.
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"math/rand"
     9  	"time"
    10  
    11  	rss "github.com/jteeuwen/go-pkg-rss"
    12  )
    13  
    14  // STARTITEM OMIT
    15  // An Item is a stripped-down RSS item.
    16  type Item struct{ Title, Channel, GUID string }
    17  
    18  // STOPITEM OMIT
    19  
    20  // STARTFETCHER OMIT
    21  // A Fetcher fetches Items and returns the time when the next fetch should be
    22  // attempted.  On failure, Fetch returns a non-nil error.
    23  type Fetcher interface {
    24  	Fetch() (items []Item, next time.Time, err error)
    25  }
    26  
    27  // STOPFETCHER OMIT
    28  
    29  // STARTSUBSCRIPTION OMIT
    30  // A Subscription delivers Items over a channel.  Close cancels the
    31  // subscription, closes the Updates channel, and returns the last fetch error,
    32  // if any.
    33  type Subscription interface {
    34  	Updates() <-chan Item
    35  	Close() error
    36  }
    37  
    38  // STOPSUBSCRIPTION OMIT
    39  
    40  // STARTSUBSCRIBE OMIT
    41  // Subscribe returns a new Subscription that uses fetcher to fetch Items.
    42  func Subscribe(fetcher Fetcher) Subscription {
    43  	s := &sub{
    44  		fetcher: fetcher,
    45  		updates: make(chan Item),       // for Updates
    46  		closing: make(chan chan error), // for Close
    47  	}
    48  	go s.loop()
    49  	return s
    50  }
    51  
    52  // STOPSUBSCRIBE OMIT
    53  
    54  // sub implements the Subscription interface.
    55  type sub struct {
    56  	fetcher Fetcher         // fetches items
    57  	updates chan Item       // sends items to the user
    58  	closing chan chan error // for Close
    59  }
    60  
    61  // STARTUPDATES OMIT
    62  func (s *sub) Updates() <-chan Item {
    63  	return s.updates
    64  }
    65  
    66  // STOPUPDATES OMIT
    67  
    68  // STARTCLOSE OMIT
    69  // STARTCLOSESIG OMIT
    70  func (s *sub) Close() error {
    71  	// STOPCLOSESIG OMIT
    72  	errc := make(chan error)
    73  	s.closing <- errc // HLchan
    74  	return <-errc     // HLchan
    75  }
    76  
    77  // STOPCLOSE OMIT
    78  
    79  // loopCloseOnly is a version of loop that includes only the logic
    80  // that handles Close.
    81  func (s *sub) loopCloseOnly() {
    82  	// STARTCLOSEONLY OMIT
    83  	var err error // set when Fetch fails
    84  	for {
    85  		select {
    86  		case errc := <-s.closing: // HLchan
    87  			errc <- err      // HLchan
    88  			close(s.updates) // tells receiver we're done
    89  			return
    90  		}
    91  	}
    92  	// STOPCLOSEONLY OMIT
    93  }
    94  
    95  // loopFetchOnly is a version of loop that includes only the logic
    96  // that calls Fetch.
    97  func (s *sub) loopFetchOnly() {
    98  	// STARTFETCHONLY OMIT
    99  	var pending []Item // appended by fetch; consumed by send
   100  	var next time.Time // initially January 1, year 0
   101  	var err error
   102  	for {
   103  		var fetchDelay time.Duration // initally 0 (no delay)
   104  		if now := time.Now(); next.After(now) {
   105  			fetchDelay = next.Sub(now)
   106  		}
   107  		startFetch := time.After(fetchDelay)
   108  
   109  		select {
   110  		case <-startFetch:
   111  			var fetched []Item
   112  			fetched, next, err = s.fetcher.Fetch()
   113  			if err != nil {
   114  				next = time.Now().Add(10 * time.Second)
   115  				break
   116  			}
   117  			pending = append(pending, fetched...)
   118  		}
   119  	}
   120  	// STOPFETCHONLY OMIT
   121  }
   122  
   123  // loopSendOnly is a version of loop that includes only the logic for
   124  // sending items to s.updates.
   125  func (s *sub) loopSendOnly() {
   126  	// STARTSENDONLY OMIT
   127  	var pending []Item // appended by fetch; consumed by send
   128  	for {
   129  		var first Item
   130  		var updates chan Item // HLupdates
   131  		if len(pending) > 0 {
   132  			first = pending[0]
   133  			updates = s.updates // enable send case // HLupdates
   134  		}
   135  
   136  		select {
   137  		case updates <- first:
   138  			pending = pending[1:]
   139  		}
   140  	}
   141  	// STOPSENDONLY OMIT
   142  }
   143  
   144  // mergedLoop is a version of loop that combines loopCloseOnly,
   145  // loopFetchOnly, and loopSendOnly.
   146  func (s *sub) mergedLoop() {
   147  	// STARTFETCHVARS OMIT
   148  	var pending []Item
   149  	var next time.Time
   150  	var err error
   151  	// STOPFETCHVARS OMIT
   152  	for {
   153  		// STARTNOCAP OMIT
   154  		var fetchDelay time.Duration
   155  		if now := time.Now(); next.After(now) {
   156  			fetchDelay = next.Sub(now)
   157  		}
   158  		startFetch := time.After(fetchDelay)
   159  		// STOPNOCAP OMIT
   160  		var first Item
   161  		var updates chan Item
   162  		if len(pending) > 0 {
   163  			first = pending[0]
   164  			updates = s.updates // enable send case
   165  		}
   166  
   167  		// STARTSELECT OMIT
   168  		select {
   169  		case errc := <-s.closing: // HLcases
   170  			errc <- err
   171  			close(s.updates)
   172  			return
   173  			// STARTFETCHCASE OMIT
   174  		case <-startFetch: // HLcases
   175  			var fetched []Item
   176  			fetched, next, err = s.fetcher.Fetch() // HLfetch
   177  			if err != nil {
   178  				next = time.Now().Add(10 * time.Second)
   179  				break
   180  			}
   181  			pending = append(pending, fetched...) // HLfetch
   182  			// STOPFETCHCASE OMIT
   183  		case updates <- first: // HLcases
   184  			pending = pending[1:]
   185  		}
   186  		// STOPSELECT OMIT
   187  	}
   188  }
   189  
   190  // dedupeLoop extends mergedLoop with deduping of fetched items.
   191  func (s *sub) dedupeLoop() {
   192  	const maxPending = 10
   193  	// STARTSEEN OMIT
   194  	var pending []Item
   195  	var next time.Time
   196  	var err error
   197  	var seen = make(map[string]bool) // set of item.GUIDs // HLseen
   198  	// STOPSEEN OMIT
   199  	for {
   200  		// STARTCAP OMIT
   201  		var fetchDelay time.Duration
   202  		if now := time.Now(); next.After(now) {
   203  			fetchDelay = next.Sub(now)
   204  		}
   205  		var startFetch <-chan time.Time // HLcap
   206  		if len(pending) < maxPending {  // HLcap
   207  			startFetch = time.After(fetchDelay) // enable fetch case  // HLcap
   208  		} // HLcap
   209  		// STOPCAP OMIT
   210  		var first Item
   211  		var updates chan Item
   212  		if len(pending) > 0 {
   213  			first = pending[0]
   214  			updates = s.updates // enable send case
   215  		}
   216  		select {
   217  		case errc := <-s.closing:
   218  			errc <- err
   219  			close(s.updates)
   220  			return
   221  		// STARTDEDUPE OMIT
   222  		case <-startFetch:
   223  			var fetched []Item
   224  			fetched, next, err = s.fetcher.Fetch() // HLfetch
   225  			if err != nil {
   226  				next = time.Now().Add(10 * time.Second)
   227  				break
   228  			}
   229  			for _, item := range fetched {
   230  				if !seen[item.GUID] { // HLdupe
   231  					pending = append(pending, item) // HLdupe
   232  					seen[item.GUID] = true          // HLdupe
   233  				} // HLdupe
   234  			}
   235  			// STOPDEDUPE OMIT
   236  		case updates <- first:
   237  			pending = pending[1:]
   238  		}
   239  	}
   240  }
   241  
   242  // loop periodically fecthes Items, sends them on s.updates, and exits
   243  // when Close is called.  It extends dedupeLoop with logic to run
   244  // Fetch asynchronously.
   245  func (s *sub) loop() {
   246  	const maxPending = 10
   247  	type fetchResult struct {
   248  		fetched []Item
   249  		next    time.Time
   250  		err     error
   251  	}
   252  	// STARTFETCHDONE OMIT
   253  	var fetchDone chan fetchResult // if non-nil, Fetch is running // HL
   254  	// STOPFETCHDONE OMIT
   255  	var pending []Item
   256  	var next time.Time
   257  	var err error
   258  	var seen = make(map[string]bool)
   259  	for {
   260  		var fetchDelay time.Duration
   261  		if now := time.Now(); next.After(now) {
   262  			fetchDelay = next.Sub(now)
   263  		}
   264  		// STARTFETCHIF OMIT
   265  		var startFetch <-chan time.Time
   266  		if fetchDone == nil && len(pending) < maxPending { // HLfetch
   267  			startFetch = time.After(fetchDelay) // enable fetch case
   268  		}
   269  		// STOPFETCHIF OMIT
   270  		var first Item
   271  		var updates chan Item
   272  		if len(pending) > 0 {
   273  			first = pending[0]
   274  			updates = s.updates // enable send case
   275  		}
   276  		// STARTFETCHASYNC OMIT
   277  		select {
   278  		case <-startFetch: // HLfetch
   279  			fetchDone = make(chan fetchResult, 1) // HLfetch
   280  			go func() {
   281  				fetched, next, err := s.fetcher.Fetch()
   282  				fetchDone <- fetchResult{fetched, next, err}
   283  			}()
   284  		case result := <-fetchDone: // HLfetch
   285  			fetchDone = nil // HLfetch
   286  			// Use result.fetched, result.next, result.err
   287  			// STOPFETCHASYNC OMIT
   288  			fetched := result.fetched
   289  			next, err = result.next, result.err
   290  			if err != nil {
   291  				next = time.Now().Add(10 * time.Second)
   292  				break
   293  			}
   294  			for _, item := range fetched {
   295  				if id := item.GUID; !seen[id] { // HLdupe
   296  					pending = append(pending, item)
   297  					seen[id] = true // HLdupe
   298  				}
   299  			}
   300  		case errc := <-s.closing:
   301  			errc <- err
   302  			close(s.updates)
   303  			return
   304  		case updates <- first:
   305  			pending = pending[1:]
   306  		}
   307  	}
   308  }
   309  
   310  // naiveMerge is a version of Merge that doesn't quite work right.  In
   311  // particular, the goroutines it starts may block forever on m.updates
   312  // if the receiver stops receiving.
   313  type naiveMerge struct {
   314  	subs    []Subscription
   315  	updates chan Item
   316  }
   317  
   318  // STARTNAIVEMERGE OMIT
   319  func NaiveMerge(subs ...Subscription) Subscription {
   320  	m := &naiveMerge{
   321  		subs:    subs,
   322  		updates: make(chan Item),
   323  	}
   324  	// STARTNAIVEMERGELOOP OMIT
   325  	for _, sub := range subs {
   326  		go func(s Subscription) {
   327  			for it := range s.Updates() {
   328  				m.updates <- it // HL
   329  			}
   330  		}(sub)
   331  	}
   332  	// STOPNAIVEMERGELOOP OMIT
   333  	return m
   334  }
   335  
   336  // STOPNAIVEMERGE OMIT
   337  
   338  // STARTNAIVEMERGECLOSE OMIT
   339  func (m *naiveMerge) Close() (err error) {
   340  	for _, sub := range m.subs {
   341  		if e := sub.Close(); err == nil && e != nil {
   342  			err = e
   343  		}
   344  	}
   345  	close(m.updates) // HL
   346  	return
   347  }
   348  
   349  // STOPNAIVEMERGECLOSE OMIT
   350  
   351  func (m *naiveMerge) Updates() <-chan Item {
   352  	return m.updates
   353  }
   354  
   355  type merge struct {
   356  	subs    []Subscription
   357  	updates chan Item
   358  	quit    chan struct{}
   359  	errs    chan error
   360  }
   361  
   362  // STARTMERGESIG OMIT
   363  // Merge returns a Subscription that merges the item streams from subs.
   364  // Closing the merged subscription closes subs.
   365  func Merge(subs ...Subscription) Subscription {
   366  	// STOPMERGESIG OMIT
   367  	m := &merge{
   368  		subs:    subs,
   369  		updates: make(chan Item),
   370  		quit:    make(chan struct{}),
   371  		errs:    make(chan error),
   372  	}
   373  	// STARTMERGE OMIT
   374  	for _, sub := range subs {
   375  		go func(s Subscription) {
   376  			for {
   377  				var it Item
   378  				select {
   379  				case it = <-s.Updates():
   380  				case <-m.quit: // HL
   381  					m.errs <- s.Close() // HL
   382  					return              // HL
   383  				}
   384  				select {
   385  				case m.updates <- it:
   386  				case <-m.quit: // HL
   387  					m.errs <- s.Close() // HL
   388  					return              // HL
   389  				}
   390  			}
   391  		}(sub)
   392  	}
   393  	// STOPMERGE OMIT
   394  	return m
   395  }
   396  
   397  func (m *merge) Updates() <-chan Item {
   398  	return m.updates
   399  }
   400  
   401  // STARTMERGECLOSE OMIT
   402  func (m *merge) Close() (err error) {
   403  	close(m.quit) // HL
   404  	for _ = range m.subs {
   405  		if e := <-m.errs; e != nil { // HL
   406  			err = e
   407  		}
   408  	}
   409  	close(m.updates) // HL
   410  	return
   411  }
   412  
   413  // STOPMERGECLOSE OMIT
   414  
   415  // NaiveDedupe converts a stream of Items that may contain duplicates
   416  // into one that doesn't.
   417  func NaiveDedupe(in <-chan Item) <-chan Item {
   418  	out := make(chan Item)
   419  	go func() {
   420  		seen := make(map[string]bool)
   421  		for it := range in {
   422  			if !seen[it.GUID] {
   423  				// BUG: this send blocks if the
   424  				// receiver closes the Subscription
   425  				// and stops receiving.
   426  				out <- it // HL
   427  				seen[it.GUID] = true
   428  			}
   429  		}
   430  		close(out)
   431  	}()
   432  	return out
   433  }
   434  
   435  type deduper struct {
   436  	s       Subscription
   437  	updates chan Item
   438  	closing chan chan error
   439  }
   440  
   441  // Dedupe converts a Subscription that may send duplicate Items into
   442  // one that doesn't.
   443  func Dedupe(s Subscription) Subscription {
   444  	d := &deduper{
   445  		s:       s,
   446  		updates: make(chan Item),
   447  		closing: make(chan chan error),
   448  	}
   449  	go d.loop()
   450  	return d
   451  }
   452  
   453  func (d *deduper) loop() {
   454  	in := d.s.Updates() // enable receive
   455  	var pending Item
   456  	var out chan Item // disable send
   457  	seen := make(map[string]bool)
   458  	for {
   459  		select {
   460  		case it := <-in:
   461  			if !seen[it.GUID] {
   462  				pending = it
   463  				in = nil        // disable receive
   464  				out = d.updates // enable send
   465  				seen[it.GUID] = true
   466  			}
   467  		case out <- pending:
   468  			in = d.s.Updates() // enable receive
   469  			out = nil          // disable send
   470  		case errc := <-d.closing:
   471  			err := d.s.Close()
   472  			errc <- err
   473  			close(d.updates)
   474  			return
   475  		}
   476  	}
   477  }
   478  
   479  func (d *deduper) Close() error {
   480  	errc := make(chan error)
   481  	d.closing <- errc
   482  	return <-errc
   483  }
   484  
   485  func (d *deduper) Updates() <-chan Item {
   486  	return d.updates
   487  }
   488  
   489  // FakeFetch causes Fetch to use a fake fetcher instead of the real
   490  // one.
   491  var FakeFetch bool
   492  
   493  // Fetch returns a Fetcher for Items from domain.
   494  func Fetch(domain string) Fetcher {
   495  	if FakeFetch {
   496  		return fakeFetch(domain)
   497  	}
   498  	return realFetch(domain)
   499  }
   500  
   501  func fakeFetch(domain string) Fetcher {
   502  	return &fakeFetcher{channel: domain}
   503  }
   504  
   505  type fakeFetcher struct {
   506  	channel string
   507  	items   []Item
   508  }
   509  
   510  // FakeDuplicates causes the fake fetcher to return duplicate items.
   511  var FakeDuplicates bool
   512  
   513  func (f *fakeFetcher) Fetch() (items []Item, next time.Time, err error) {
   514  	now := time.Now()
   515  	next = now.Add(time.Duration(rand.Intn(5)) * 500 * time.Millisecond)
   516  	item := Item{
   517  		Channel: f.channel,
   518  		Title:   fmt.Sprintf("Item %d", len(f.items)),
   519  	}
   520  	item.GUID = item.Channel + "/" + item.Title
   521  	f.items = append(f.items, item)
   522  	if FakeDuplicates {
   523  		items = f.items
   524  	} else {
   525  		items = []Item{item}
   526  	}
   527  	return
   528  }
   529  
   530  // realFetch returns a fetcher for the specified blogger domain.
   531  func realFetch(domain string) Fetcher {
   532  	return NewFetcher(fmt.Sprintf("http://%s/feeds/posts/default?alt=rss", domain))
   533  }
   534  
   535  type fetcher struct {
   536  	uri   string
   537  	feed  *rss.Feed
   538  	items []Item
   539  }
   540  
   541  // NewFetcher returns a Fetcher for uri.
   542  func NewFetcher(uri string) Fetcher {
   543  	f := &fetcher{
   544  		uri: uri,
   545  	}
   546  	newChans := func(feed *rss.Feed, chans []*rss.Channel) {}
   547  	newItems := func(feed *rss.Feed, ch *rss.Channel, items []*rss.Item) {
   548  		for _, it := range items {
   549  			f.items = append(f.items, Item{
   550  				Channel: ch.Title,
   551  				GUID:    it.Guid,
   552  				Title:   it.Title,
   553  			})
   554  		}
   555  	}
   556  	f.feed = rss.New(1 /*minimum interval in minutes*/, true /*respect limit*/, newChans, newItems)
   557  	return f
   558  }
   559  
   560  func (f *fetcher) Fetch() (items []Item, next time.Time, err error) {
   561  	fmt.Println("fetching", f.uri)
   562  	if err = f.feed.Fetch(f.uri, nil); err != nil {
   563  		return
   564  	}
   565  	items = f.items
   566  	f.items = nil
   567  	next = time.Now().Add(time.Duration(f.feed.SecondsTillUpdate()) * time.Second)
   568  	return
   569  }
   570  
   571  // TODO: in a longer talk: move the Subscribe function onto a Reader type, to
   572  // support dynamically adding and removing Subscriptions.  Reader should dedupe.
   573  
   574  // TODO: in a longer talk: make successive Subscribe calls for the same uri
   575  // share the same underlying Subscription, but provide duplicate streams.
   576  
   577  func init() {
   578  	rand.Seed(time.Now().UnixNano())
   579  }
   580  
   581  // STARTMAIN OMIT
   582  func main() {
   583  	// STARTMERGECALL OMIT
   584  	// Subscribe to some feeds, and create a merged update stream.
   585  	merged := Merge(
   586  		Subscribe(Fetch("blog.golang.org")),
   587  		Subscribe(Fetch("googleblog.blogspot.com")),
   588  		Subscribe(Fetch("googledevelopers.blogspot.com")))
   589  	// STOPMERGECALL OMIT
   590  
   591  	// Close the subscriptions after some time.
   592  	time.AfterFunc(3*time.Second, func() {
   593  		fmt.Println("closed:", merged.Close())
   594  	})
   595  
   596  	// Print the stream.
   597  	for it := range merged.Updates() {
   598  		fmt.Println(it.Channel, it.Title)
   599  	}
   600  
   601  	// Uncomment the panic below to dump the stack traces.  This
   602  	// will show several stacks for persistent HTTP connections
   603  	// created by the real RSS client.  To clean these up, we'll
   604  	// need to extend Fetcher with a Close method and plumb this
   605  	// through the RSS client implementation.
   606  	//
   607  	// panic("show me the stacks")
   608  }
   609  
   610  // STOPMAIN OMIT