forked from syncthing/syncthing
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lib/model: Add support for different puller block ordering (syncthing…
…#6587) * WIP * Tests * Header and format * WIP * Fix tests * Goland you disappoint me * Remove CC storage * Update blockpullreorderer.go
- Loading branch information
1 parent
decb967
commit 6201eeb
Showing
5 changed files
with
289 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// Copyright (C) 2020 The Syncthing Authors. | ||
// | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||
// You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
package config | ||
|
||
type BlockPullOrder int | ||
|
||
const ( | ||
BlockPullOrderStandard BlockPullOrder = iota // default is standard | ||
BlockPullOrderRandom | ||
BlockPullOrderInOrder | ||
) | ||
|
||
func (o BlockPullOrder) String() string { | ||
switch o { | ||
case BlockPullOrderStandard: | ||
return "standard" | ||
case BlockPullOrderRandom: | ||
return "random" | ||
case BlockPullOrderInOrder: | ||
return "inOrder" | ||
default: | ||
return "unknown" | ||
} | ||
} | ||
|
||
func (o BlockPullOrder) MarshalText() ([]byte, error) { | ||
return []byte(o.String()), nil | ||
} | ||
|
||
func (o *BlockPullOrder) UnmarshalText(bs []byte) error { | ||
switch string(bs) { | ||
case "standard": | ||
*o = BlockPullOrderStandard | ||
case "random": | ||
*o = BlockPullOrderRandom | ||
case "inOrder": | ||
*o = BlockPullOrderInOrder | ||
default: | ||
*o = BlockPullOrderStandard | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// Copyright (C) 2020 The Syncthing Authors. | ||
// | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||
// You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
package model | ||
|
||
import ( | ||
"github.com/syncthing/syncthing/lib/config" | ||
"github.com/syncthing/syncthing/lib/protocol" | ||
"github.com/syncthing/syncthing/lib/rand" | ||
"sort" | ||
) | ||
|
||
type blockPullReorderer interface { | ||
Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo | ||
} | ||
|
||
func newBlockPullReorderer(order config.BlockPullOrder, id protocol.DeviceID, otherDevices []protocol.DeviceID) blockPullReorderer { | ||
switch order { | ||
case config.BlockPullOrderRandom: | ||
return randomOrderBlockPullReorderer{} | ||
case config.BlockPullOrderInOrder: | ||
return inOrderBlockPullReorderer{} | ||
case config.BlockPullOrderStandard: | ||
fallthrough | ||
default: | ||
return newStandardBlockPullReorderer(id, otherDevices) | ||
} | ||
} | ||
|
||
type inOrderBlockPullReorderer struct{} | ||
|
||
func (inOrderBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo { | ||
return blocks | ||
} | ||
|
||
type randomOrderBlockPullReorderer struct{} | ||
|
||
func (randomOrderBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo { | ||
rand.Shuffle(blocks) | ||
return blocks | ||
} | ||
|
||
type standardBlockPullReorderer struct { | ||
myIndex int | ||
count int | ||
shuffle func(interface{}) // Used for test | ||
} | ||
|
||
func newStandardBlockPullReorderer(id protocol.DeviceID, otherDevices []protocol.DeviceID) *standardBlockPullReorderer { | ||
allDevices := append(otherDevices, id) | ||
sort.Slice(allDevices, func(i, j int) bool { | ||
return allDevices[i].Compare(allDevices[j]) == -1 | ||
}) | ||
// Find our index | ||
myIndex := -1 | ||
for i, dev := range allDevices { | ||
if dev == id { | ||
myIndex = i | ||
break | ||
} | ||
} | ||
if myIndex < 0 { | ||
panic("bug: could not find my own index") | ||
} | ||
return &standardBlockPullReorderer{ | ||
myIndex: myIndex, | ||
count: len(allDevices), | ||
shuffle: rand.Shuffle, | ||
} | ||
} | ||
|
||
func (p *standardBlockPullReorderer) Reorder(blocks []protocol.BlockInfo) []protocol.BlockInfo { | ||
if len(blocks) == 0 { | ||
return blocks | ||
} | ||
|
||
// Split the blocks into len(allDevices) chunks. Chunk count might be less than device count, if there are more | ||
// devices than blocks. | ||
chunks := chunk(blocks, p.count) | ||
|
||
newBlocks := make([]protocol.BlockInfo, 0, len(blocks)) | ||
|
||
// First add our own chunk. We might fall off the list if there are more devices than chunks... | ||
if p.myIndex < len(chunks) { | ||
newBlocks = append(newBlocks, chunks[p.myIndex]...) | ||
} | ||
|
||
// The rest of the chunks we fetch in a random order in whole chunks. | ||
// Generate chunk index slice and shuffle it | ||
indexes := make([]int, 0, len(chunks)-1) | ||
for i := 0; i < len(chunks); i++ { | ||
if i != p.myIndex { | ||
indexes = append(indexes, i) | ||
} | ||
} | ||
|
||
p.shuffle(indexes) | ||
|
||
// Append the chunks in the order of the index slices. | ||
for _, idx := range indexes { | ||
newBlocks = append(newBlocks, chunks[idx]...) | ||
} | ||
|
||
return newBlocks | ||
} | ||
|
||
func chunk(blocks []protocol.BlockInfo, partCount int) [][]protocol.BlockInfo { | ||
if partCount == 0 { | ||
return [][]protocol.BlockInfo{blocks} | ||
} | ||
count := len(blocks) | ||
chunkSize := (count + partCount - 1) / partCount | ||
parts := make([][]protocol.BlockInfo, 0, partCount) | ||
for i := 0; i < count; i += chunkSize { | ||
end := i + chunkSize | ||
if end > count { | ||
end = count | ||
} | ||
parts = append(parts, blocks[i:end]) | ||
} | ||
return parts | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// Copyright (C) 2020 The Syncthing Authors. | ||
// | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this file, | ||
// You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
package model | ||
|
||
import ( | ||
"github.com/syncthing/syncthing/lib/protocol" | ||
"reflect" | ||
"sort" | ||
"testing" | ||
) | ||
|
||
var ( | ||
someBlocks = []protocol.BlockInfo{{Offset: 1}, {Offset: 2}, {Offset: 3}} | ||
) | ||
|
||
func Test_chunk(t *testing.T) { | ||
type args struct { | ||
blocks []protocol.BlockInfo | ||
partCount int | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want [][]protocol.BlockInfo | ||
}{ | ||
{"one", args{someBlocks, 1}, [][]protocol.BlockInfo{someBlocks}}, | ||
{"two", args{someBlocks, 2}, [][]protocol.BlockInfo{someBlocks[:2], someBlocks[2:]}}, | ||
{"three", args{someBlocks, 3}, [][]protocol.BlockInfo{someBlocks[:1], someBlocks[1:2], someBlocks[2:]}}, | ||
{"four", args{someBlocks, 4}, [][]protocol.BlockInfo{someBlocks[:1], someBlocks[1:2], someBlocks[2:]}}, | ||
// Never happens as myIdx would be -1, so we'd return in order. | ||
{"zero", args{someBlocks, 0}, [][]protocol.BlockInfo{someBlocks}}, | ||
{"empty-one", args{nil, 1}, [][]protocol.BlockInfo{}}, | ||
{"empty-zero", args{nil, 0}, [][]protocol.BlockInfo{nil}}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := chunk(tt.args.blocks, tt.args.partCount); !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("chunk() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Test_inOrderBlockPullReorderer_Reorder(t *testing.T) { | ||
type args struct { | ||
blocks []protocol.BlockInfo | ||
} | ||
tests := []struct { | ||
name string | ||
blocks []protocol.BlockInfo | ||
want []protocol.BlockInfo | ||
}{ | ||
{"basic", someBlocks, someBlocks}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
in := inOrderBlockPullReorderer{} | ||
if got := in.Reorder(tt.blocks); !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("Reorder() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func Test_standardBlockPullReorderer_Reorder(t *testing.T) { | ||
// Order the devices, so we know their ordering ahead of time. | ||
devices := []protocol.DeviceID{myID, device1, device2} | ||
sort.Slice(devices, func(i, j int) bool { | ||
return devices[i].Compare(devices[j]) == -1 | ||
}) | ||
|
||
blocks := func(i ...int) []protocol.BlockInfo { | ||
b := make([]protocol.BlockInfo, 0, len(i)) | ||
for _, v := range i { | ||
b = append(b, protocol.BlockInfo{Offset: int64(v)}) | ||
} | ||
return b | ||
} | ||
tests := []struct { | ||
name string | ||
myId protocol.DeviceID | ||
devices []protocol.DeviceID | ||
blocks []protocol.BlockInfo | ||
want []protocol.BlockInfo | ||
}{ | ||
{"front", devices[0], []protocol.DeviceID{devices[1], devices[2]}, blocks(1, 2, 3), blocks(1, 2, 3)}, | ||
{"back", devices[2], []protocol.DeviceID{devices[0], devices[1]}, blocks(1, 2, 3), blocks(3, 1, 2)}, | ||
{"few-blocks", devices[2], []protocol.DeviceID{devices[0], devices[1]}, blocks(1), blocks(1)}, | ||
{"more-than-one-block", devices[1], []protocol.DeviceID{devices[0]}, blocks(1, 2, 3, 4), blocks(3, 4, 1, 2)}, | ||
{"empty-blocks", devices[0], []protocol.DeviceID{devices[1]}, blocks(), blocks()}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
p := newStandardBlockPullReorderer(tt.myId, tt.devices) | ||
p.shuffle = func(i interface{}) {} // Noop shuffle | ||
if got := p.Reorder(tt.blocks); !reflect.DeepEqual(got, tt.want) { | ||
t.Errorf("reorderBlocksForDevices() = %v, want %v (my idx: %d, count %d)", got, tt.want, p.myIndex, p.count) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters