3131# FIXME: unknown license
3232###########################################################################
3333from multiprocessing .pool import IMapIterator , IMapUnorderedIterator
34- from commoncode import filetype
34+
3535def wrapped (func ):
3636 def wrap (self , timeout = None ):
3737 return func (self , timeout = timeout or 1e10 )
3838 return wrap
39+
3940IMapIterator .next = wrapped (IMapIterator .next )
4041IMapIterator .__next__ = IMapIterator .next
4142IMapUnorderedIterator .next = wrapped (IMapUnorderedIterator .next )
@@ -61,10 +62,12 @@ def wrap(self, timeout=None):
6162
6263from commoncode import ignore
6364from commoncode import fileutils
65+ from commoncode import filetype
6466
6567from scancode import __version__ as version
6668
6769from scancode .interrupt import interruptible
70+ from scancode .interrupt import time_and_ram_interruptible
6871from scancode import utils
6972
7073from scancode .cache import get_scans_cache_class
@@ -403,7 +406,7 @@ def scan_event(item):
403406 click .secho (' ' + errored_path , fg = 'red' , err = to_stdout )
404407
405408 click .secho ('Scan statistics: %(files_count)d files scanned in %(total_time)ds.' % locals (), err = to_stdout )
406- click .secho ('Scan options: %(_scans)s with %(processes)d processes .' % locals (), err = to_stdout )
409+ click .secho ('Scan options: %(_scans)s with %(processes)d processe(s) .' % locals (), err = to_stdout )
407410 click .secho ('Scanning speed: {:.2} files per sec.' .format (files_scanned_per_second ), err = to_stdout )
408411 click .secho ('Scanning time: %(scanning_time)ds.' % locals (), err = to_stdout , reset = True ,)
409412 click .secho ('Indexing time: %(indexing_time)ds.' % locals (), err = to_stdout )
@@ -416,11 +419,17 @@ def scan_event(item):
416419TEST_TIMEOUT = 0
417420MAX_SCAN_TIMEOUT = 600
418421
422+ TEST_MAX_MEMORY = 0 #1024 * 1024 * 1024 * 80 # 100MB
423+ MAX_SCAN_MEMORY = 1024 * 1024 * 1024 * 1024 # 1GB
424+
425+ SCANCODE_EXPERIMENTAL_MAX_MEMORY = os .environ .get ('SCANCODE_EXPERIMENTAL_MAX_MEMORY' , False )
426+
419427
420428def scan_timeout (size ):
421429 """
422- Return a timeout in seconds computed based on a file size.
430+ Return a scan timeout in seconds computed based on a file size.
423431 """
432+ # at least 60 seconds
424433 timeout = 60
425434 if size > 1024 * 1024 * 1024 :
426435 # add extra seconds for each megabyte
@@ -430,6 +439,20 @@ def scan_timeout(size):
430439 return timeout
431440
432441
442+ def scan_max_memory (size ):
443+ """
444+ Return a scan not-to-exceed maximum memory in bytes computed based on a file size.
445+ """
446+
447+ max_memory = 1024 * 1024 * 1024 * 700 # 700MB
448+ if size > 1024 * 1024 * 1024 :
449+ # add extra quota for each byte: 5x
450+ max_memory += size * 5
451+
452+ max_memory = min ((max_memory , MAX_SCAN_MEMORY ))
453+ return max_memory
454+
455+
433456def _scanit (paths , scanners , scans_cache_class ):
434457 """
435458 Run scans and cache results. Used as an execution unit for parallel processing.
@@ -449,11 +472,18 @@ def _scanit(paths, scanners, scans_cache_class):
449472 # ENSURE we only do tghis for files not directories
450473 if not is_cached :
451474 # run the scan as an interruptiple task
452- # use TEST_TIMEOUT for tests if provided
453- timeout = TEST_TIMEOUT or scan_timeout (infos .get ('size' , 0 ))
454475 scans_runner = partial (scan_one , abs_path , scanners )
455- success , scan_result = interruptible (scans_runner , timeout = timeout )
456476
477+ file_size = infos .get ('size' , 0 )
478+ # use TEST_TIMEOUT for tests if provided
479+ timeout = TEST_TIMEOUT or scan_timeout (file_size )
480+ # feature switch
481+ if SCANCODE_EXPERIMENTAL_MAX_MEMORY :
482+ # use TEST_MAX_MEMORY for tests if provided
483+ max_memory = TEST_MAX_MEMORY or scan_max_memory (file_size )
484+ success , scan_result = time_and_ram_interruptible (scans_runner , timeout = timeout , max_memory = max_memory )
485+ else :
486+ success , scan_result = interruptible (scans_runner , timeout = timeout )
457487 if not success :
458488 # Use scan errors as the scan result for that file on failure
459489 scan_result = dict (scan_errors = [scan_result , '' ])
0 commit comments