6
6
"github.com/skynetlabs/pinner/lib"
7
7
"strings"
8
8
"sync"
9
+ "sync/atomic"
9
10
"time"
10
11
11
12
"github.com/skynetlabs/pinner/conf"
@@ -109,11 +110,16 @@ type (
109
110
Scanner struct {
110
111
staticDB * database.DB
111
112
staticLogger logger.Logger
113
+ staticScannerThreads int
112
114
staticServerName string
113
115
staticSkydClient skyd.Client
114
116
staticSleepBetweenScans time.Duration
115
117
staticTG * threadgroup.ThreadGroup
116
118
119
+ // Stats variables:
120
+ atomicCountPinned uint32
121
+ scanStart time.Time
122
+
117
123
dryRun bool
118
124
minPinners int
119
125
// skipSkylinks is a list of skylinks which we want to skip during this
@@ -124,14 +130,15 @@ type (
124
130
)
125
131
126
132
// NewScanner creates a new Scanner instance.
127
- func NewScanner (db * database.DB , logger logger.Logger , minPinners int , serverName string , customSleepBetweenScans time.Duration , skydClient skyd.Client ) * Scanner {
133
+ func NewScanner (db * database.DB , logger logger.Logger , minPinners int , threads int , serverName string , customSleepBetweenScans time.Duration , skydClient skyd.Client ) * Scanner {
128
134
sleep := customSleepBetweenScans
129
135
if sleep == 0 {
130
136
sleep = sleepBetweenScans
131
137
}
132
138
return & Scanner {
133
139
staticDB : db ,
134
140
staticLogger : logger ,
141
+ staticScannerThreads : threads ,
135
142
staticServerName : serverName ,
136
143
staticSkydClient : skydClient ,
137
144
staticSleepBetweenScans : sleep ,
@@ -238,7 +245,31 @@ func (s *Scanner) threadedScanAndPin() {
238
245
s .staticLogger .Tracef ("Start scanning" )
239
246
s .managedRefreshDryRun ()
240
247
s .managedRefreshMinPinners ()
241
- s .managedPinUnderpinnedSkylinks ()
248
+ s .managedResetSkippedSkylinks ()
249
+ s .managedResetStats ()
250
+
251
+ // Start a thread that will print intermediate scanning statistics.
252
+ statsCh := make (chan struct {})
253
+ err = s .staticTG .Add ()
254
+ if err != nil {
255
+ return // the threadgroup is stopped
256
+ }
257
+ go s .threadedPrintStats (statsCh )
258
+
259
+ // Start N threads that will scan for underpinned skylinks and repin
260
+ // them. It's possible that at first all of those start pinning skylinks
261
+ // without properly respecting the MaxRepairingSkylinks limit. That's
262
+ // expected and chosen because of the simplicity of the implementation.
263
+ var wg sync.WaitGroup
264
+ for i := 0 ; i < s .staticScannerThreads ; i ++ {
265
+ wg .Add (1 )
266
+ go func () {
267
+ defer wg .Done ()
268
+ s .managedPinUnderpinnedSkylinks ()
269
+ }()
270
+ }
271
+ wg .Wait ()
272
+ close (statsCh )
242
273
s .staticLogger .Tracef ("End scanning" )
243
274
244
275
// Schedule the next scan, unless already scheduled:
@@ -249,6 +280,54 @@ func (s *Scanner) threadedScanAndPin() {
249
280
}
250
281
}
251
282
283
+ // threadedPrintStats prints regular updates on the scanning process plus a
284
+ // final overview of the pinned and skipped skylinks.
285
+ func (s * Scanner ) threadedPrintStats (stopCh chan struct {}) {
286
+ defer s .staticTG .Done ()
287
+ intermediateStatsTicker := time .NewTicker (printPinningStatisticsPeriod )
288
+ defer intermediateStatsTicker .Stop ()
289
+
290
+ select {
291
+ case <- intermediateStatsTicker .C :
292
+ // Print intermediate statistics.
293
+ t1 := lib .Now ()
294
+ s .mu .Lock ()
295
+ numSkipped := len (s .skipSkylinks )
296
+ startTime := s .scanStart
297
+ s .mu .Unlock ()
298
+ s .staticLogger .Infof ("Time %s, runtime %s, pinned skylinks %d, skipped skylinks %d" ,
299
+ t1 .Format (conf .TimeFormat ), t1 .Sub (startTime ).String (), atomic .LoadUint32 (& s .atomicCountPinned ), numSkipped )
300
+ case <- stopCh :
301
+ // Print final statistics when finishing the method.
302
+ t1 := lib .Now ()
303
+ s .mu .Lock ()
304
+ skipped := s .skipSkylinks
305
+ startTime := s .scanStart
306
+ s .mu .Unlock ()
307
+ s .staticLogger .Infof ("Finished at %s, runtime %s, pinned skylinks %d, skipped skylinks %d" ,
308
+ t1 .Format (conf .TimeFormat ), t1 .Sub (startTime ).String (), atomic .LoadUint32 (& s .atomicCountPinned ), len (skipped ))
309
+ s .staticLogger .Tracef ("Skipped %d skylinks: %v" , len (skipped ), skipped )
310
+ case <- s .staticTG .StopChan ():
311
+ s .staticLogger .Trace ("Stop channel closed" )
312
+ return
313
+ }
314
+ }
315
+
316
+ // managedResetSkippedSkylinks resets the skipped skylinks.
317
+ func (s * Scanner ) managedResetSkippedSkylinks () {
318
+ s .mu .Lock ()
319
+ s .skipSkylinks = []string {}
320
+ s .mu .Unlock ()
321
+ }
322
+
323
+ // managedResetStats resets the scanning statistics.
324
+ func (s * Scanner ) managedResetStats () {
325
+ s .mu .Lock ()
326
+ s .scanStart = lib .Now ()
327
+ s .mu .Unlock ()
328
+ atomic .StoreUint32 (& s .atomicCountPinned , 0 )
329
+ }
330
+
252
331
// staticScheduleNextScan attempts to set the time of the next scan until either we
253
332
// succeed, another server succeeds, or Scanner's TG is stopped. Returns true
254
333
// when Scanner's TG is stopped.
@@ -292,26 +371,6 @@ func (s *Scanner) managedPinUnderpinnedSkylinks() {
292
371
s .staticLogger .Trace ("Entering managedPinUnderpinnedSkylinks" )
293
372
defer s .staticLogger .Trace ("Exiting managedPinUnderpinnedSkylinks" )
294
373
295
- // Clear out the skipped skylinks from the previous run.
296
- s .mu .Lock ()
297
- s .skipSkylinks = []string {}
298
- s .mu .Unlock ()
299
-
300
- intermediateStatsTicker := time .NewTicker (printPinningStatisticsPeriod )
301
- defer intermediateStatsTicker .Stop ()
302
- countPinned := 0
303
- t0 := lib .Now ()
304
-
305
- // Print final statistics when finishing the method.
306
- defer func () {
307
- t1 := lib .Now ()
308
- s .mu .Lock ()
309
- skipped := s .skipSkylinks
310
- s .mu .Unlock ()
311
- s .staticLogger .Infof ("Finished at %s, runtime %s, pinned skylinks %d, skipped skylinks %d" , t1 .Format (conf .TimeFormat ), t1 .Sub (t0 ).String (), countPinned , len (skipped ))
312
- s .staticLogger .Tracef ("Skipped %d skylinks: %v" , len (skipped ), skipped )
313
- }()
314
-
315
374
for {
316
375
// Check for service shutdown before talking to the DB.
317
376
select {
@@ -321,21 +380,11 @@ func (s *Scanner) managedPinUnderpinnedSkylinks() {
321
380
default :
322
381
}
323
382
324
- // Print intermediate statistics.
325
- select {
326
- case <- intermediateStatsTicker .C :
327
- t1 := lib .Now ()
328
- s .mu .Lock ()
329
- numSkipped := len (s .skipSkylinks )
330
- s .mu .Unlock ()
331
- s .staticLogger .Infof ("Time %s, runtime %s, pinned skylinks %d, skipped skylinks %d" , t1 .Format (conf .TimeFormat ), t1 .Sub (t0 ).String (), countPinned , numSkipped )
332
- default :
333
- }
334
-
335
383
skylink , sp , continueScanning , err := s .managedFindAndPinOneUnderpinnedSkylink ()
336
384
if ! sp .IsEmpty () {
337
- countPinned ++
338
- } else {
385
+ atomic .AddUint32 (& s .atomicCountPinned , 1 )
386
+ }
387
+ if err != nil {
339
388
s .staticLogger .Trace (err )
340
389
}
341
390
if ! continueScanning {
@@ -345,7 +394,7 @@ func (s *Scanner) managedPinUnderpinnedSkylinks() {
345
394
// already logged and the only indication it gives us is whether we
346
395
// should wait for the file we pinned to become healthy or not. If there
347
396
// is an error, then there is nothing to wait for.
348
- if err == nil && ! sp .IsEmpty () {
397
+ if ! sp .IsEmpty () {
349
398
// Block until the pinned skylink becomes healthy or until a timeout.
350
399
s .staticWaitUntilHealthy (skylink , sp )
351
400
continue
@@ -378,7 +427,6 @@ func (s *Scanner) managedFindAndPinOneUnderpinnedSkylink() (skylink skymodules.S
378
427
s .mu .Unlock ()
379
428
380
429
ctx := context .TODO ()
381
-
382
430
sl , err := s .staticDB .FindAndLockUnderpinned (ctx , s .staticServerName , skipSkylinks , minPinners )
383
431
if database .IsNoSkylinksNeedPinning (err ) {
384
432
return skymodules.Skylink {}, skymodules.SiaPath {}, false , err
@@ -466,9 +514,9 @@ func (s *Scanner) managedSkipSkylink(sl skymodules.Skylink) {
466
514
// we pin another one. It returns a ballpark value.
467
515
//
468
516
// This method makes some assumptions for simplicity:
469
- // * assumes lazy pinning, meaning that none of the fanout is uploaded
470
- // * all skyfiles are assumed to be large files (base sector + fanout) and the
471
- // metadata is assumed to fill up the base sector (to err on the safe side)
517
+ // - assumes lazy pinning, meaning that none of the fanout is uploaded
518
+ // - all skyfiles are assumed to be large files (base sector + fanout) and the
519
+ // metadata is assumed to fill up the base sector (to err on the safe side)
472
520
func (s * Scanner ) staticEstimateTimeToFull (skylink skymodules.Skylink ) time.Duration {
473
521
meta , err := s .staticSkydClient .Metadata (skylink .String ())
474
522
if err != nil {
@@ -527,7 +575,8 @@ func (s *Scanner) staticEligibleToPin(ctx context.Context) (bool, error) {
527
575
pinnedData , err := s .staticDB .ServerLoad (ctx , s .staticServerName )
528
576
if errors .Contains (err , database .ErrServerLoadNotFound ) {
529
577
// We don't know what the server's load is. Get that data.
530
- load , err := s .staticSkydClient .ContractData ()
578
+ var load uint64
579
+ load , err = s .staticSkydClient .ContractData ()
531
580
if err != nil {
532
581
return false , errors .AddContext (err , "failed to fetch server's load" )
533
582
}
0 commit comments