Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ The following help text is displayed for ScanCode version 32.0.0:
-n, --processes INT Set the number of parallel processes to use. Disable
parallel processing if 0. Also disable threading if
-1. [default: (number of CPUs)-1]
-c, --config-file FILENAME Path to the configuration file.
--config-file FILENAME Path to the configuration file.
-q, --quiet Do not print summary or progress.
-v, --verbose Print progress as file-by-file path instead of a
progress bar. Print verbose scan counters.
Expand Down
2 changes: 1 addition & 1 deletion docs/source/rst-snippets/cli-core-options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

Default: ``(number of CPUs)-1``

-c, --config-file FILENAME Path to the configuration file.
--config-file FILENAME Path to the configuration file.
-v, --verbose Print verbose file-by-file progress messages.

-q, --quiet Do not print summary or progress messages.
Expand Down
2 changes: 1 addition & 1 deletion src/scancode/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def default_processes():
cls=PluggableCommandLineOption,
)

@click.option('-c', '--config-file',
@click.option('--config-file',
type=click.File('r'),
required=False,
help='Path to the configuration file.',
Expand Down
158 changes: 81 additions & 77 deletions src/scancode/interrupt.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,79 @@ class TimeoutError(Exception): # NOQA
NO_ERROR = None
NO_VALUE = None

from ctypes import c_long
from ctypes import py_object
from ctypes import pythonapi
from multiprocessing import TimeoutError as MpTimeoutError

from queue import Empty as Queue_Empty
from queue import Queue
from _thread import start_new_thread

def async_raise(tid, exctype=Exception):
"""
Raise an Exception in the Thread with id `tid`. Perform cleanup if
needed.

Based on Killable Threads By Tomer Filiba
from http://tomerfiliba.com/recipes/Thread2/
license: public domain.
"""
assert isinstance(tid, int), 'Invalid thread id: must an integer'

tid = c_long(tid)
exception = py_object(Exception)
res = pythonapi.PyThreadState_SetAsyncExc(tid, exception)
if res == 0:
raise ValueError('Invalid thread id.')
elif res != 1:
# if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect
pythonapi.PyThreadState_SetAsyncExc(tid, 0)
raise SystemError('PyThreadState_SetAsyncExc failed.')

def thread_interruptible(func, args=None, kwargs=None, timeout=DEFAULT_TIMEOUT):
"""
Threads-based interruptible runner. It can work on both Windows and POSIX,
but is not reliable and works only if everything is pickable.
"""
# We run `func` in a thread and block on a queue until timeout
results = Queue()

def runner():
"""
Run the func and send results back in a queue as a tuple of
(`error`, `value`)
"""
try:
_res = func(*(args or ()), **(kwargs or {}))
results.put((NO_ERROR, _res,))
except Exception:
results.put((ERROR_MSG + traceback_format_exc(), NO_VALUE,))

tid = start_new_thread(runner, ())

try:
# wait for the queue results up to timeout
err_res = results.get(timeout=timeout)

if not err_res:
return ERROR_MSG, NO_VALUE

return err_res

except (Queue_Empty, MpTimeoutError):
return TIMEOUT_MSG % locals(), NO_VALUE

except Exception:
return ERROR_MSG + traceback_format_exc(), NO_VALUE

finally:
try:
async_raise(tid, Exception)
except (SystemExit, ValueError):
pass

if not on_windows:
"""
Some code based in part and inspired from the RobotFramework and
Expand Down Expand Up @@ -93,92 +166,23 @@ def handler(signum, frame):
except TimeoutError:
return TIMEOUT_MSG % locals(), NO_VALUE

except Exception:
except ValueError as ve:
if 'signal only works in main thread' in str(ve):
# Fallback to the thread-based implementation if we are not in the main thread of the main interpreter
return thread_interruptible(func, args, kwargs, timeout)
return ERROR_MSG + traceback_format_exc(), NO_VALUE

finally:
setitimer(ITIMER_REAL, 0)

elif on_windows:
"""
Run a function in an interruptible thread with a timeout.
Based on an idea of dano "Dan O'Reilly"
http://stackoverflow.com/users/2073595/dano
But no code has been reused from this post.
"""

from ctypes import c_long
from ctypes import py_object
from ctypes import pythonapi
from multiprocessing import TimeoutError as MpTimeoutError

from queue import Empty as Queue_Empty
from queue import Queue
from _thread import start_new_thread

def interruptible(func, args=None, kwargs=None, timeout=DEFAULT_TIMEOUT):
"""
Windows, threads-based interruptible runner. It can work also on
POSIX, but is not reliable and works only if everything is pickable.
"""
# We run `func` in a thread and block on a queue until timeout
results = Queue()

def runner():
"""
Run the func and send results back in a queue as a tuple of
(`error`, `value`)
"""
try:
_res = func(*(args or ()), **(kwargs or {}))
results.put((NO_ERROR, _res,))
except Exception:
results.put((ERROR_MSG + traceback_format_exc(), NO_VALUE,))

tid = start_new_thread(runner, ())

try:
# wait for the queue results up to timeout
err_res = results.get(timeout=timeout)

if not err_res:
return ERROR_MSG, NO_VALUE

return err_res

except (Queue_Empty, MpTimeoutError):
return TIMEOUT_MSG % locals(), NO_VALUE

except Exception:
return ERROR_MSG + traceback_format_exc(), NO_VALUE

finally:
try:
async_raise(tid, Exception)
except (SystemExit, ValueError):
setitimer(ITIMER_REAL, 0)
except ValueError:
pass

def async_raise(tid, exctype=Exception):
"""
Raise an Exception in the Thread with id `tid`. Perform cleanup if
needed.

Based on Killable Threads By Tomer Filiba
from http://tomerfiliba.com/recipes/Thread2/
license: public domain.
"""
assert isinstance(tid, int), 'Invalid thread id: must an integer'

tid = c_long(tid)
exception = py_object(Exception)
res = pythonapi.PyThreadState_SetAsyncExc(tid, exception)
if res == 0:
raise ValueError('Invalid thread id.')
elif res != 1:
# if it returns a number greater than one, you're in trouble,
# and you should call it again with exc=NULL to revert the effect
pythonapi.PyThreadState_SetAsyncExc(tid, 0)
raise SystemError('PyThreadState_SetAsyncExc failed.')
elif on_windows:
interruptible = thread_interruptible


def fake_interruptible(func, args=None, kwargs=None, timeout=DEFAULT_TIMEOUT):
Expand Down
45 changes: 22 additions & 23 deletions tests/scancode/data/help/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,29 +136,28 @@ Options:
which are todo items and needs manual review.

core:
--ignore <pattern> Ignore files matching <pattern>.
--timeout <seconds> Stop an unfinished file scan after a timeout in
seconds. [default: 120 seconds]
-n, --processes INT Set the number of parallel processes to use.
Disable parallel processing if 0. Also disable
threading if -1. [default: (number of CPUs)-1]
-c, --config-file FILENAME Path to the configuration file.
-q, --quiet Do not print summary or progress.
-v, --verbose Print progress as file-by-file path instead of a
progress bar. Print verbose scan counters.
--from-json Load codebase from one or more <input> JSON scan
file(s).
--max-in-memory INTEGER Maximum number of files and directories scan
details kept in memory during a scan. Additional
files and directories scan details above this
number are cached on-disk rather than in memory.
Use 0 to use unlimited memory and disable on-disk
caching. Use -1 to use only on-disk caching.
[default: 10000]
--max-depth INTEGER Maximum nesting depth of subdirectories to scan.
Descend at most INTEGER levels of directories
below and including the starting directory. Use 0
for no scan depth limit.
--ignore <pattern> Ignore files matching <pattern>.
--timeout <seconds> Stop an unfinished file scan after a timeout in
seconds. [default: 120 seconds]
-n, --processes INT Set the number of parallel processes to use. Disable
parallel processing if 0. Also disable threading if
-1. [default: (number of CPUs)-1]
--config-file FILENAME Path to the configuration file.
-q, --quiet Do not print summary or progress.
-v, --verbose Print progress as file-by-file path instead of a
progress bar. Print verbose scan counters.
--from-json Load codebase from one or more <input> JSON scan
file(s).
--max-in-memory INTEGER Maximum number of files and directories scan details
kept in memory during a scan. Additional files and
directories scan details above this number are cached
on-disk rather than in memory. Use 0 to use unlimited
memory and disable on-disk caching. Use -1 to use
only on-disk caching. [default: 10000]
--max-depth INTEGER Maximum nesting depth of subdirectories to scan.
Descend at most INTEGER levels of directories below
and including the starting directory. Use 0 for no
scan depth limit.

documentation:
-h, --help Show this message and exit.
Expand Down
45 changes: 22 additions & 23 deletions tests/scancode/data/help/help_linux.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,29 +138,28 @@ Options:
which are todo items and needs manual review.

core:
--ignore <pattern> Ignore files matching <pattern>.
--timeout <seconds> Stop an unfinished file scan after a timeout in
seconds. [default: 120 seconds]
-n, --processes INT Set the number of parallel processes to use.
Disable parallel processing if 0. Also disable
threading if -1. [default: (number of CPUs)-1]
-c, --config-file FILENAME Path to the configuration file.
-q, --quiet Do not print summary or progress.
-v, --verbose Print progress as file-by-file path instead of a
progress bar. Print verbose scan counters.
--from-json Load codebase from one or more <input> JSON scan
file(s).
--max-in-memory INTEGER Maximum number of files and directories scan
details kept in memory during a scan. Additional
files and directories scan details above this
number are cached on-disk rather than in memory.
Use 0 to use unlimited memory and disable on-disk
caching. Use -1 to use only on-disk caching.
[default: 10000]
--max-depth INTEGER Maximum nesting depth of subdirectories to scan.
Descend at most INTEGER levels of directories
below and including the starting directory. Use 0
for no scan depth limit.
--ignore <pattern> Ignore files matching <pattern>.
--timeout <seconds> Stop an unfinished file scan after a timeout in
seconds. [default: 120 seconds]
-n, --processes INT Set the number of parallel processes to use. Disable
parallel processing if 0. Also disable threading if
-1. [default: (number of CPUs)-1]
--config-file FILENAME Path to the configuration file.
-q, --quiet Do not print summary or progress.
-v, --verbose Print progress as file-by-file path instead of a
progress bar. Print verbose scan counters.
--from-json Load codebase from one or more <input> JSON scan
file(s).
--max-in-memory INTEGER Maximum number of files and directories scan details
kept in memory during a scan. Additional files and
directories scan details above this number are cached
on-disk rather than in memory. Use 0 to use unlimited
memory and disable on-disk caching. Use -1 to use
only on-disk caching. [default: 10000]
--max-depth INTEGER Maximum nesting depth of subdirectories to scan.
Descend at most INTEGER levels of directories below
and including the starting directory. Use 0 for no
scan depth limit.

documentation:
-h, --help Show this message and exit.
Expand Down