github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/fuzzer/queue/distributor.go (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package queue 5 6 import ( 7 "sync" 8 "sync/atomic" 9 10 "github.com/google/syzkaller/pkg/stat" 11 ) 12 13 // Distributor distributes requests to different VMs during input triage 14 // (allows to avoid already used VMs). 15 type Distributor struct { 16 source Source 17 seq atomic.Uint64 18 empty atomic.Bool 19 active atomic.Pointer[[]atomic.Uint64] 20 mu sync.Mutex 21 queue []*Request 22 statDelayed *stat.Val 23 statUndelayed *stat.Val 24 statViolated *stat.Val 25 } 26 27 func Distribute(source Source) *Distributor { 28 return &Distributor{ 29 source: source, 30 statDelayed: stat.New("distributor delayed", "Number of test programs delayed due to VM avoidance", 31 stat.Graph("distributor")), 32 statUndelayed: stat.New("distributor undelayed", "Number of test programs undelayed for VM avoidance", 33 stat.Graph("distributor")), 34 statViolated: stat.New("distributor violated", "Number of test programs violated VM avoidance", 35 stat.Graph("distributor")), 36 } 37 } 38 39 // Next returns the next request to execute on the given vm. 40 func (dist *Distributor) Next(vm int) *Request { 41 dist.noteActive(vm) 42 if req := dist.delayed(vm); req != nil { 43 return req 44 } 45 for { 46 req := dist.source.Next() 47 if req == nil || !contains(req.Avoid, vm) || !dist.hasOtherActive(req.Avoid) { 48 return req 49 } 50 dist.delay(req) 51 } 52 } 53 54 func (dist *Distributor) delay(req *Request) { 55 dist.mu.Lock() 56 defer dist.mu.Unlock() 57 req.delayedSince = dist.seq.Load() 58 dist.queue = append(dist.queue, req) 59 dist.statDelayed.Add(1) 60 dist.empty.Store(false) 61 } 62 63 func (dist *Distributor) delayed(vm int) *Request { 64 if dist.empty.Load() { 65 return nil 66 } 67 dist.mu.Lock() 68 defer dist.mu.Unlock() 69 seq := dist.seq.Load() 70 for i, req := range dist.queue { 71 violation := contains(req.Avoid, vm) 72 // The delayedSince check protects from a situation when we had another VM available, 73 // and delayed a request, but then the VM was taken for reproduction and does not 74 // serve requests any more. If we could not dispatch a request in 1000 attempts, 75 // we gave up and give it to any VM. 76 if violation && req.delayedSince+1000 > seq { 77 continue 78 } 79 dist.statUndelayed.Add(1) 80 if violation { 81 dist.statViolated.Add(1) 82 } 83 last := len(dist.queue) - 1 84 dist.queue[i] = dist.queue[last] 85 dist.queue[last] = nil 86 dist.queue = dist.queue[:last] 87 dist.empty.Store(len(dist.queue) == 0) 88 return req 89 } 90 return nil 91 } 92 93 func (dist *Distributor) noteActive(vm int) { 94 active := dist.active.Load() 95 if active == nil || len(*active) <= vm { 96 dist.mu.Lock() 97 active = dist.active.Load() 98 if active == nil || len(*active) <= vm { 99 tmp := make([]atomic.Uint64, vm+10) 100 active = &tmp 101 dist.active.Store(active) 102 } 103 dist.mu.Unlock() 104 } 105 (*active)[vm].Store(dist.seq.Add(1)) 106 } 107 108 // hasOtherActive says if we recently seen activity from VMs not in the set. 109 func (dist *Distributor) hasOtherActive(set []ExecutorID) bool { 110 seq := dist.seq.Load() 111 active := *dist.active.Load() 112 for vm := range active { 113 if contains(set, vm) { 114 continue 115 } 116 // 1000 is semi-random notion of recency. 117 if active[vm].Load()+1000 < seq { 118 continue 119 } 120 return true 121 } 122 return false 123 } 124 125 func contains(set []ExecutorID, vm int) bool { 126 for _, id := range set { 127 if id.VM == vm { 128 return true 129 } 130 } 131 return false 132 }