cmd/stdiscosrv: Sort addresses before replication (fixes #6093) (#6094)

This makes sure addresses are sorted when coming in from the API. The
database merge operation still checks for correct ordering (which is
quick) and sorts if it isn't correct (legacy database record or
replication peer), but then does a copy first.

Tested with -race in production...
This commit is contained in:
Jakob Borg 2019-10-18 10:50:19 +02:00 committed by GitHub
parent c4f161d8c5
commit 9084510e1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 6 deletions

View File

@ -18,6 +18,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -279,6 +280,10 @@ func (s *apiSrv) handleAnnounce(remote net.IP, deviceID protocol.DeviceID, addre
dbAddrs[i].Expires = expire dbAddrs[i].Expires = expire
} }
// The address slice must always be sorted for database merges to work
// properly.
sort.Sort(databaseAddressOrder(dbAddrs))
seen := now.UnixNano() seen := now.UnixNano()
if s.repl != nil { if s.repl != nil {
s.repl.send(key, dbAddrs, seen) s.repl.send(key, dbAddrs, seen)

View File

@ -10,6 +10,7 @@
package main package main
import ( import (
"log"
"sort" "sort"
"time" "time"
@ -263,12 +264,15 @@ func (s *levelDBStore) Stop() {
// chosen for any duplicates. // chosen for any duplicates.
func merge(a, b DatabaseRecord) DatabaseRecord { func merge(a, b DatabaseRecord) DatabaseRecord {
// Both lists must be sorted for this to work. // Both lists must be sorted for this to work.
sort.Slice(a.Addresses, func(i, j int) bool { if !sort.IsSorted(databaseAddressOrder(a.Addresses)) {
return a.Addresses[i].Address < a.Addresses[j].Address log.Println("Warning: bug: addresses not correctly sorted in merge")
}) a.Addresses = sortedAddressCopy(a.Addresses)
sort.Slice(b.Addresses, func(i, j int) bool { }
return b.Addresses[i].Address < b.Addresses[j].Address if !sort.IsSorted(databaseAddressOrder(b.Addresses)) {
}) // no warning because this is the side we read from disk and it may
// legitimately predate correct sorting.
b.Addresses = sortedAddressCopy(b.Addresses)
}
res := DatabaseRecord{ res := DatabaseRecord{
Addresses: make([]DatabaseAddress, 0, len(a.Addresses)+len(b.Addresses)), Addresses: make([]DatabaseAddress, 0, len(a.Addresses)+len(b.Addresses)),
@ -352,3 +356,24 @@ func expire(addrs []DatabaseAddress, now int64) []DatabaseAddress {
} }
return addrs return addrs
} }
func sortedAddressCopy(addrs []DatabaseAddress) []DatabaseAddress {
sorted := make([]DatabaseAddress, len(addrs))
copy(sorted, addrs)
sort.Sort(databaseAddressOrder(sorted))
return sorted
}
type databaseAddressOrder []DatabaseAddress
func (s databaseAddressOrder) Less(a, b int) bool {
return s[a].Address < s[b].Address
}
func (s databaseAddressOrder) Swap(a, b int) {
s[a], s[b] = s[b], s[a]
}
func (s databaseAddressOrder) Len() int {
return len(s)
}