github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/internal/init/handle_init_download.go (about) 1 // Copyright 2021 The TrueBlocks Authors. All rights reserved. 2 // Use of this source code is governed by a license that can 3 // be found in the LICENSE file. 4 5 package initPkg 6 7 import ( 8 "errors" 9 "fmt" 10 "runtime" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 16 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/colors" 17 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/config" 18 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/index" 19 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/logger" 20 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/manifest" 21 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/progress" 22 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types" 23 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/walk" 24 ) 25 26 var m sync.Mutex 27 28 // TODO: So we can capture both the blooms and the index portions in one summary. Once we move to single stream, this can go local 29 var nProcessed int 30 var nStarted int 31 32 // downloadAndReportProgress Downloads the chunks and reports progress to the progressChannel 33 func (opts *InitOptions) downloadAndReportProgress(chunks []types.ChunkRecord, chunkType walk.CacheType, nTotal int) ([]types.ChunkRecord, bool) { 34 chain := opts.Globals.Chain 35 sleep := base.Max(.0125, opts.Sleep) 36 successCount := 0 37 38 failed := []types.ChunkRecord{} 39 cancelled := false 40 41 // Establish a channel to listen for progress messages 42 progressChannel := progress.MakeChan() 43 defer close(progressChannel) 44 45 // If we make this too big, the pinning service chokes 46 poolSize := runtime.NumCPU() * 2 47 48 // Start the go routine that downloads the chunks. This sends messages through the progressChannel 49 go index.DownloadChunks(chain, chunks, chunkType, poolSize, progressChannel) 50 51 for event := range progressChannel { 52 chunk, ok := event.Payload.(*types.ChunkRecord) 53 var rng string 54 if ok { 55 rng = chunk.Range 56 } 57 58 if event.Event == progress.Cancelled { 59 cancelled = true 60 break 61 } 62 63 if event.Event == progress.AllDone { 64 msg := fmt.Sprintf("%sCompleted initializing %s files.%s", colors.BrightWhite, chunkType, colors.Off) 65 logger.Info(msg, strings.Repeat(" ", 60)) 66 break 67 } 68 69 // TODO: is this a performance issue? 70 m.Lock() // To conflict progress printing 71 switch event.Event { 72 case progress.Error: 73 logger.Error(event.Error) 74 if ok { 75 failed = append(failed, *chunk) 76 if errors.Is(event.Error, index.ErrWriteToDiscError) { 77 sleep = base.Min(4, sleep*1.2) 78 successCount = 0 79 } 80 } 81 82 case progress.Start: 83 nStarted++ 84 if nStarted <= 50 { // we don't need too many of these 85 logger.Info("Started download of", event.Message, "[", nStarted, " of ", nTotal, "]") 86 } 87 if nStarted == poolSize*3 { 88 msg := fmt.Sprintf("%sPlease wait...%s", colors.BrightWhite, colors.Off) 89 logger.Info(msg) 90 } 91 92 case progress.Update: 93 msg := fmt.Sprintf("%s%s%s", colors.Yellow, event.Message, colors.Off) 94 logger.Info(msg, spaces) 95 96 case progress.Finished: 97 nProcessed++ 98 col := colors.Yellow 99 if event.Message == "bloom" { 100 col = colors.Magenta 101 } 102 successCount++ 103 pct := float64(nProcessed) / float64(nTotal) * 100 104 sleepStr := "" 105 if sleep > .25 { 106 sleepStr = fmt.Sprintf(" [sleep: %0.2fms]", sleep*1000) 107 } 108 msg := fmt.Sprintf("Finished download of %s%s%s %s%s%s (% 4d of %4d %0.1f%%%s) chain: %s", col, event.Message, colors.Off, col, rng, colors.Off, nProcessed, nTotal, pct, sleepStr, opts.Globals.Chain) 109 logger.Info(msg, spaces) 110 if successCount%10 == 0 { 111 sleep = base.Max(.0125, sleep/1.2) 112 successCount = 0 113 } 114 115 default: 116 logger.Info(event.Message, rng, spaces) 117 } 118 m.Unlock() 119 120 if sleep > 0 { 121 ms := time.Duration(sleep*1000) * time.Millisecond 122 time.Sleep(ms) 123 } 124 } 125 126 return failed, cancelled 127 } 128 129 // retry retries downloading any `failedChunks`. It repeats `nTimes` times by calling `downloadChunks` function. 130 // 131 // Returns number of chunks that we were unable to fetch. This function is simple because: 132 // 1. it will never get a new failing chunk (it only feeds in the list of known, failed chunks) 133 // 2. The maximum number of failing chunks we can get equals the length of `failedChunks`. 134 // 135 // TODO: Instead of storing failed attempts in an array and retrying them after processing the entire list in the manifest, 136 // TODO: we want to re-process failed downloads on the stop. In that way, we can do progressive backoff per chunk (as opposed 137 // TODO: to globally). We want to back-off on single chunks instead of every chunk. The backoff routine carries an 'attempts' 138 // TODO: value and we wait after each failure 2^nAttempts (double the wait each time it fails). Max 10 tries or something. 139 func retry(failedChunks []types.ChunkRecord, nTimes int, downloadChunksFunc func(chunks []types.ChunkRecord) (failed []types.ChunkRecord, cancelled bool)) int { 140 count := 0 141 142 chunksToRetry := failedChunks 143 cancelled := false 144 145 for { 146 if len(chunksToRetry) == 0 { 147 break 148 } 149 150 if count >= nTimes { 151 break 152 } 153 154 logger.Warn(colors.Yellow, "Retrying", len(chunksToRetry), "downloads", colors.Off) 155 if chunksToRetry, cancelled = downloadChunksFunc(chunksToRetry); cancelled { 156 break 157 } 158 159 count++ 160 } 161 162 return len(chunksToRetry) 163 } 164 165 // updateLocalManifest updates the local manifest with the one downloaded but may add existing chunks if they are later... 166 func (opts *InitOptions) updateLocalManifest(existing, remote *manifest.Manifest) error { 167 chain := opts.Globals.Chain 168 169 copy := *remote 170 171 if existing != nil { 172 lastExisting := base.RangeFromRangeString(existing.Chunks[len(existing.Chunks)-1].Range) 173 lastRemote := base.RangeFromRangeString(remote.Chunks[len(remote.Chunks)-1].Range) 174 if !lastExisting.LaterThan(lastRemote) { 175 for _, ch := range existing.Chunks { 176 rng := base.RangeFromRangeString(ch.Range) 177 if rng.LaterThan(lastRemote) { 178 copy.Chunks = append(copy.Chunks, ch) 179 } 180 } 181 } 182 } 183 184 return copy.SaveManifest(chain, config.PathToManifest(chain)) 185 } 186 187 var spaces = strings.Repeat(" ", 55)