Simplify main fetching/sorting logic

Reduced the amount of go-routines. Now sorting
is done in the same go-routine as URLs are fetched.

Then these sorted lists are merged one by one
in a loop as they arrive.

This greatly reduces complexity.
This commit is contained in:
Julian Ospald 2017-09-11 15:52:26 +02:00
parent ebbcd71b3d
commit 9476a00648
1 changed files with 25 additions and 57 deletions

View File

@ -62,25 +62,29 @@ func numbersHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Non-blocking input channel for URL results. // Non-blocking channel for sorted results
// We will read as much as we can at once. sortChan := make(chan []int, len(rurl))
inputChan := make(chan []int, len(rurl))
var wg sync.WaitGroup var wg sync.WaitGroup
// fetch all URLs asynchronously // fetch all URLs asynchronously and sort them
for i := range rurl { for i := range rurl {
wg.Add(1) wg.Add(1)
go func(url string) { go func(url string) {
defer wg.Done() defer wg.Done()
n, e := getNumbers(url, ctx) n, e := getNumbers(url, ctx)
if e == nil { if e == nil { // we have a usable JSON list of numbers
if n != nil && len(n) > 0 { if n != nil && len(n) > 0 { // the list of numbers is non-empty
inputChan <- n sorted, err := sort.SortedAndDedup(ctx, n)
} else { if err != nil { // sorting threw an error
log.Printf("Sorting took too long, ignoring list")
return
}
sortChan <- sorted
} else { // empty list of numbers
log.Printf("Received empty list of numbers from endpoint") log.Printf("Received empty list of numbers from endpoint")
} }
} else { } else { // no usable JSON/other error
log.Printf("Got an error: %s", e) log.Printf("Got an error: %s", e)
} }
}(rurl[i]) }(rurl[i])
@ -88,69 +92,33 @@ func numbersHandler(w http.ResponseWriter, r *http.Request) {
// master routine closing the inputChan // master routine closing the inputChan
go func() { go func() {
wg.Wait() wg.Wait()
close(inputChan) close(sortChan)
}() }()
// channel for sorting process, so we can short-circuit in // result list that is returned on short-circuit
// case sorting takes too long var resultList []int = []int{}
sortChan := make(chan []int, 1)
// aggregate numbers from URLs
var numberBuffer []int = []int{}
// these are actually sorted
var sortedNumbers []int = []int{}
// aggregate and sort loop, // get all sorted lists concurrently and merge them as we go
// breaks if all URLs have been processed or the timeout
// has been reached
done := false done := false
for done != true { for done != true {
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Printf("Waiting for URL took too long, finishing response anyway") log.Printf("Waiting for URL took too long, finishing response anyway")
finishResponse(w, sortedNumbers) finishResponse(w, resultList)
return return
case res, more := <-inputChan: case res, more := <-sortChan:
if more { // still URLs to fetch if more {
numberBuffer = append(numberBuffer, res...) merged := sort.MergeLists(resultList, res)
// continue to aggregate numbers from the buffer resultList = merged
continue } else {
} else { // all URLs fetched, sort and be done log.Printf("Nothing else to sort")
log.Printf("Nothing else to fetch")
done = true done = true
} }
// non-blocking branch that sorts what we already have
// we are not done here yet
default:
// only sort if we have new results
if len(numberBuffer) == 0 {
continue
}
}
// sort fallthrough, either the inputChan is currently "empty"
// or we fetched all URLs already
go func(sorted []int, unsorted_buffer []int) {
res, err := sort.SortedAndDedup(ctx, unsorted_buffer)
if err != nil {
return
}
merged := sort.MergeLists(sortedNumbers, res)
sortChan <- merged
}(sortedNumbers, numberBuffer)
numberBuffer = []int{}
select {
case merged := <-sortChan:
sortedNumbers = merged
case <-ctx.Done():
log.Printf("Sorting took too long, finishing response anyway")
finishResponse(w, sortedNumbers)
return
} }
} }
log.Printf("Result is complete, finishing response") log.Printf("Result is complete, finishing response")
finishResponse(w, sortedNumbers) finishResponse(w, resultList)
} }
// Finalizes the JSON response with the given numbers. This always // Finalizes the JSON response with the given numbers. This always