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