Skip to content

Commit 4b4643a

Browse files
authored
Allow for number of scanners to be configurable (#762)
Signed-off-by: egibs <20933572+egibs@users.noreply.github.com>
1 parent 7dc95ab commit 4b4643a

File tree

6 files changed

+44
-59
lines changed

6 files changed

+44
-59
lines changed

cmd/mal/mal.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ var (
6363
outputFlag string
6464
profileFlag bool
6565
quantityIncreasesRiskFlag bool
66+
scannersFlag int
6667
statsFlag bool
6768
thirdPartyFlag bool
6869
verboseFlag bool
@@ -92,7 +93,7 @@ func showError(err error) {
9293
fmt.Fprintf(os.Stderr, "%s %s\n", emoji, err.Error())
9394
}
9495

95-
//nolint:cyclop // ignore complexity of 40
96+
//nolint:cyclop,gocognit // ignore complexity of 40,100
9697
func main() {
9798
returnCode := ExitOK
9899
defer func() { os.Exit(returnCode) }()
@@ -249,9 +250,14 @@ func main() {
249250
concurrency = 1
250251
}
251252

253+
maxScanners := scannersFlag
254+
if maxScanners > concurrency {
255+
maxScanners = concurrency
256+
}
257+
252258
var pool *malcontent.ScannerPool
253259
if mc.ScannerPool == nil {
254-
pool, err = malcontent.NewScannerPool(yrs, concurrency)
260+
pool, err = malcontent.NewScannerPool(yrs, maxScanners)
255261
if err != nil {
256262
returnCode = ExitInvalidRules
257263
}
@@ -264,6 +270,7 @@ func main() {
264270
IgnoreSelf: ignoreSelfFlag,
265271
IgnoreTags: ignoreTags,
266272
IncludeDataFiles: includeDataFiles,
273+
MaxScanners: maxScanners,
267274
MinFileRisk: minFileRisk,
268275
MinRisk: minRisk,
269276
OCI: ociFlag,
@@ -372,6 +379,12 @@ func main() {
372379
Usage: "Increase file risk score based on behavior quantity",
373380
Destination: &quantityIncreasesRiskFlag,
374381
},
382+
&cli.IntFlag{
383+
Name: "scanners",
384+
Value: runtime.NumCPU(),
385+
Usage: "Number of scanners to create",
386+
Destination: &scannersFlag,
387+
},
375388
&cli.BoolFlag{
376389
Name: "stats",
377390
Aliases: []string{"s"},

pkg/action/scan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func scanSinglePath(ctx context.Context, c malcontent.Config, path string, ruleF
5757

5858
var pool *malcontent.ScannerPool
5959
if c.ScannerPool == nil {
60-
pool, err = malcontent.NewScannerPool(yrs, c.Concurrency)
60+
pool, err = malcontent.NewScannerPool(yrs, c.MaxScanners)
6161
if err != nil {
6262
return nil, fmt.Errorf("failed to create scanner pool: %w", err)
6363
}

pkg/malcontent/malcontent.go

Lines changed: 23 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"runtime"
1212
"sync"
1313
"sync/atomic"
14-
"time"
1514

1615
yarax "github.com/VirusTotal/yara-x/go"
1716
orderedmap "github.com/wk8/go-ordered-map/v2"
@@ -34,6 +33,7 @@ type Config struct {
3433
IgnoreSelf bool
3534
IgnoreTags []string
3635
IncludeDataFiles bool
36+
MaxScanners int
3737
MinFileRisk int
3838
MinRisk int
3939
OCI bool
@@ -175,23 +175,17 @@ func NewScannerPool(rules *yarax.Rules, maxScanners int) (*ScannerPool, error) {
175175
available: make(chan *yarax.Scanner, maxScanners),
176176
maxScanners: int32(maxScanners),
177177
scanners: make([]*yarax.Scanner, 0, maxScanners),
178+
closed: atomic.Bool{},
178179
}
179180

180-
pool.closed.Store(false)
181-
182-
// Create a subset of the maximum number of scanners to avoid contention
183-
initialScanners := maxScanners/2 + 1
184-
for i := 0; i < initialScanners; i++ {
185-
scanner, err := pool.createScanner()
186-
if err != nil {
187-
pool.Cleanup()
188-
return nil, fmt.Errorf("failed to create initial scanner: %w", err)
189-
}
190-
pool.scanners = append(pool.scanners, scanner)
191-
pool.available <- scanner
192-
atomic.AddInt32(&pool.currentCount, 1)
181+
scanner := yarax.NewScanner(rules)
182+
if scanner == nil {
183+
return nil, fmt.Errorf("failed to create scanner")
193184
}
194185

186+
pool.available <- scanner
187+
atomic.AddInt32(&pool.currentCount, 1)
188+
195189
return pool, nil
196190
}
197191

@@ -236,39 +230,27 @@ func (p *ScannerPool) Get() (*yarax.Scanner, error) {
236230
return nil, fmt.Errorf("scanner pool is closed")
237231
}
238232

233+
// Retrieve an existing scanner
234+
// If none are available, create up to the maximum number of scanners
239235
select {
240236
case scanner := <-p.available:
241-
if scanner == nil {
242-
return nil, fmt.Errorf("received nil scanner from pool")
243-
}
244237
return scanner, nil
245-
case <-time.After(100 * time.Millisecond):
246-
}
247-
248-
// Create a new scanner if we aren't already running the maximum number
249-
p.mu.Lock()
250-
current := atomic.LoadInt32(&p.currentCount)
251-
if current < p.maxScanners {
252-
scanner, err := p.createScanner()
253-
if err != nil {
238+
default:
239+
p.mu.Lock()
240+
if atomic.LoadInt32(&p.currentCount) < p.maxScanners {
241+
scanner, err := p.createScanner()
242+
if err != nil {
243+
p.mu.Unlock()
244+
return nil, fmt.Errorf("create scanner: %w", err)
245+
}
246+
p.scanners = append(p.scanners, scanner)
247+
atomic.AddInt32(&p.currentCount, 1)
254248
p.mu.Unlock()
255-
return nil, fmt.Errorf("create scanner: %w", err)
249+
return scanner, nil
256250
}
257-
p.scanners = append(p.scanners, scanner)
258-
atomic.AddInt32(&p.currentCount, 1)
259251
p.mu.Unlock()
260-
return scanner, nil
261-
}
262-
p.mu.Unlock()
263252

264-
select {
265-
case scanner := <-p.available:
266-
if scanner == nil {
267-
return nil, fmt.Errorf("received nil scanner from pool")
268-
}
269-
return scanner, nil
270-
case <-time.After(10 * time.Second):
271-
return nil, fmt.Errorf("timeout waiting for available scanner")
253+
return <-p.available, nil
272254
}
273255
}
274256

@@ -277,20 +259,7 @@ func (p *ScannerPool) Put(scanner *yarax.Scanner) {
277259
if scanner == nil || p.closed.Load() {
278260
return
279261
}
280-
281-
select {
282-
case p.available <- scanner:
283-
default:
284-
p.mu.Lock()
285-
defer func() {
286-
p.mu.Unlock()
287-
if atomic.LoadInt32(&p.currentCount) > p.maxScanners/2 {
288-
runtime.GC()
289-
}
290-
}()
291-
scanner.Destroy()
292-
atomic.AddInt32(&p.currentCount, -1)
293-
}
262+
p.available <- scanner
294263
}
295264

296265
// Cleanup destroys all scanners in the pool.

pkg/refresh/action.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func actionRefresh(ctx context.Context) ([]TestData, error) {
6969
c := &malcontent.Config{
7070
Concurrency: runtime.NumCPU(),
7171
IgnoreSelf: false,
72+
MaxScanners: runtime.NumCPU(),
7273
MinFileRisk: 0,
7374
MinRisk: 0,
7475
OCI: false,
@@ -81,7 +82,7 @@ func actionRefresh(ctx context.Context) ([]TestData, error) {
8182

8283
var pool *malcontent.ScannerPool
8384
if c.ScannerPool == nil {
84-
pool, err = malcontent.NewScannerPool(yrs, c.Concurrency)
85+
pool, err = malcontent.NewScannerPool(yrs, c.MaxScanners)
8586
if err != nil {
8687
return nil, err
8788
}

pkg/refresh/diff.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ func diffRefresh(ctx context.Context, rc Config) ([]TestData, error) {
196196
Concurrency: runtime.NumCPU(),
197197
FileRiskChange: td.riskChange,
198198
FileRiskIncrease: td.riskIncrease,
199+
MaxScanners: runtime.NumCPU(),
199200
MinFileRisk: minFileRisk,
200201
MinRisk: minRisk,
201202
QuantityIncreasesRisk: true,
@@ -207,7 +208,7 @@ func diffRefresh(ctx context.Context, rc Config) ([]TestData, error) {
207208

208209
var pool *malcontent.ScannerPool
209210
if c.ScannerPool == nil {
210-
pool, err = malcontent.NewScannerPool(yrs, c.Concurrency)
211+
pool, err = malcontent.NewScannerPool(yrs, c.MaxScanners)
211212
if err != nil {
212213
return nil, err
213214
}

pkg/refresh/refresh.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ func newConfig(rc Config) *malcontent.Config {
7474
return &malcontent.Config{
7575
Concurrency: runtime.NumCPU(),
7676
IgnoreTags: []string{"harmless"},
77+
MaxScanners: runtime.NumCPU(),
7778
MinFileRisk: 1,
7879
MinRisk: 1,
7980
QuantityIncreasesRisk: true,

0 commit comments

Comments
 (0)