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  }