Skip to content

Commit ebed01a

Browse files
authored
Merge pull request #369 from nexB/long-scan-timeout
Long scan timeout for #267
2 parents ce9d060 + e87e381 commit ebed01a

28 files changed

Lines changed: 3317 additions & 273 deletions

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def read(*names, **kwargs):
7979
'zc.lockfile >= 1.0.0, < 2.0.0',
8080
'yg.lockfile >= 2.0.0, < 3.0.0',
8181
'diskcache >= 2.0.0, < 3.0.0',
82+
'psutil >= 5.0.0, < 6.0.0',
8283

8384
# textcode
8485
'Beautifulsoup >= 3.2.0, <4.0.0',

src/scancode/cache.py

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from __future__ import absolute_import, print_function
2626

2727
from collections import OrderedDict
28+
from functools import partial
2829
import os
2930
import sys
3031

@@ -74,16 +75,10 @@ class ScanCache(object):
7475
This is NOT thread-safe, but is multi-process safe.
7576
"""
7677
def __init__(self, cache_dir):
77-
fileutils.create_dir(cache_dir)
78-
79-
# create a unique temp directory in cache_dir
80-
self.cache_base_dir = fileutils.get_temp_dir(cache_dir, prefix=timeutils.time2tstamp() + '-')
81-
82-
# and subdirs for infos and scans caches
78+
self.cache_base_dir = cache_dir
79+
# subdirs for infos and scans caches
8380
self.cache_infos_dir = os.path.join(self.cache_base_dir, 'infos')
84-
fileutils.create_dir(self.cache_infos_dir)
8581
self.cache_scans_dir = os.path.join(self.cache_base_dir, 'scans')
86-
fileutils.create_dir(self.cache_scans_dir)
8782

8883
# workaround for https://github.com/grantjenks/python-diskcache/issues/32
8984
from diskcache import Disk
@@ -93,7 +88,7 @@ def __init__(self, directory, size_threshold, pickle_protocol):
9388
super(DiskWithNoHighPickleProtocol, self).__init__(directory, size_threshold, pickle_protocol)
9489
self._protocol = 0
9590

96-
# and finially cache instances
91+
# and finally cache instances
9792
from diskcache import Cache
9893
self.infos = Cache(self.cache_infos_dir, disk=DiskWithNoHighPickleProtocol)
9994
self.scans = Cache(self.cache_scans_dir, disk=DiskWithNoHighPickleProtocol)
@@ -112,29 +107,27 @@ def put_infos(self, path, file_infos):
112107
in file_infos has already been scanned or False otherwise.
113108
"""
114109
self.infos.set(path, file_infos)
115-
has_cached_details = self.scan_key(path, file_infos) in self.scans
110+
is_scan_cached = self.scan_key(path, file_infos) in self.scans
116111
if TRACE:
117-
logger_debug('put_infos:', 'path:', path, 'has_cached_details:', has_cached_details, 'file_infos:', file_infos, '\n')
112+
logger_debug('put_infos:', 'path:', path, 'is_scan_cached:', is_scan_cached, 'file_infos:', file_infos, '\n')
118113
logger_debug('put_infos:', 'cached_infos:', self.infos[path], '\n')
119-
120-
return has_cached_details
114+
return is_scan_cached
121115

122116
def put_scan(self, path, file_infos, scan_result):
123117
"""
124-
Put scan_result in the cache. Also put file_infos in the cache if needed.
118+
Put scan_result in the cache if not already cached.
125119
"""
126-
is_cached = self.put_infos(path, file_infos)
127-
if not is_cached:
128-
scan_key = self.scan_key(path, file_infos)
129-
self.scans.add(scan_key, scan_result)
130-
if TRACE:
131-
logger_debug('put_scan:', 'scan_key:', scan_key, 'file_infos:', file_infos, 'scan_result:', scan_result, '\n')
132-
logger_debug('put_scan:', 'cached_infos:', self.infos[path], '\n')
133-
logger_debug('put_scan:', 'scan_key:', scan_key, 'cached_scan:', self.scans[scan_key], '\n')
120+
scan_key = self.scan_key(path, file_infos)
121+
self.scans.add(scan_key, scan_result)
122+
if TRACE:
123+
logger_debug('put_scan:', 'scan_key:', scan_key, 'file_infos:', file_infos, 'scan_result:', scan_result, '\n')
124+
logger_debug('put_scan:', 'cached_infos:', self.infos[path], '\n')
125+
logger_debug('put_scan:', 'scan_key:', scan_key, 'cached_scan:', self.scans[scan_key], '\n')
134126

135127
def iterate(self, with_infos=True):
136128
"""
137-
Return an iterator of scan data for all cached scans e.g. the whole cache.
129+
Yield scan data for all cached scans e.g. the whole cache.
130+
If a scan is missing for a given info, an error is appended to scan_errors.
138131
"""
139132
for path in self.infos:
140133
file_infos = self.infos[path]
@@ -144,28 +137,55 @@ def iterate(self, with_infos=True):
144137
# we flatten these as direct attributes of a file object
145138
scan_result.update(file_infos.items())
146139
else:
147-
# always report errors
140+
# always include errors even if empty
148141
scan_result['scan_errors'] = file_infos.get('scan_errors', [])
149142

143+
no_scan_details = dict(scan_errors=[
144+
('ERROR: Requested scan details unavailable in cache.',
145+
'This is either a bug or processing was aborted with CTRL-C.')]
146+
)
147+
150148
scan_key = self.scan_key(path, file_infos)
151-
scan_details = self.scans[scan_key].items()
152-
for scan_name, scan_data in scan_details:
153-
if scan_name == 'scan_errors':
154-
scan_result['scan_errors'].extend(scan_data)
155-
else:
156-
scan_result[scan_name] = scan_data
149+
scan_details = self.scans.get(scan_key, no_scan_details)
157150
if TRACE:
158151
logger_debug('iterate:', 'scan_details:', scan_details, 'for path:', path, 'scan_key:', scan_key, '\n')
152+
153+
# append errors to other top level errors if any
154+
scan_errors = scan_details.pop('scan_errors', [])
155+
scan_result['scan_errors'].extend(scan_errors)
156+
157+
scan_result.update(scan_details)
159158
yield scan_result
160159

160+
def close(self):
161+
"""
162+
Close the underlying caches.
163+
"""
164+
if self.infos:
165+
self.infos.close()
166+
if self.scans:
167+
self.scans.close()
168+
161169
def clear(self, *args):
162170
"""
163171
Purge the cache by deleting the corresponding cached data files.
164172
"""
165-
self.infos.close()
166-
self.scans.close()
173+
self.close()
167174
fileutils.delete(self.cache_base_dir)
168175

169176

170-
def get_scans_cache():
171-
return ScanCache(cache_dir=scans_cache_dir)
177+
def get_scans_cache(cache_dir=scans_cache_dir):
178+
"""
179+
Return a new unique persistent cache instance.
180+
"""
181+
return ScanCache(cache_dir)
182+
183+
184+
def get_scans_cache_class(cache_dir=scans_cache_dir):
185+
"""
186+
Return a new unique persistent cache instance.
187+
"""
188+
fileutils.create_dir(cache_dir)
189+
# create a unique temp directory in cache_dir
190+
cache_dir = fileutils.get_temp_dir(cache_dir, prefix=timeutils.time2tstamp() + '-')
191+
return partial(ScanCache, cache_dir)

0 commit comments

Comments
 (0)