// 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 ( "sort" "github.com/syncthing/syncthing/lib/config" "github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/rand" ) 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 }