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