Skip to content

Commit 5b351b6

Browse files
authored
handle SIGPIPE errors when piping output to another process (#73)
* handle SIGPIPE errors when piping output to another process * fix SIGINT handling within threaded queue * attempt to fix CI tests * remove test print * another attempt at CI tests fix * fix handler to store cursor in handle_results * use queue.join() unless we're running on python2 * remove SIGPIPE handling * - fix logging stacks - fix printed exception on broken pipe due to stdout being closed prematurely * just catch broken pipe so we don't close stdout/err on tests * remove py2 workaround from Worker * - apparently queue.join() doesn't handle SIGINT on Windows even on py3 - print on exit to not leave progress bar on current line * uncomment flush func
1 parent 7d52e47 commit 5b351b6

File tree

7 files changed

+40
-11
lines changed

7 files changed

+40
-11
lines changed

src/code42cli/cmds/alerts/extraction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def extract(sdk, profile, output_logger, args):
3737
args: Command line args used to build up alert query filters.
3838
"""
3939
store = AlertCursorStore(profile.name) if args.incremental else None
40-
handlers = create_handlers(output_logger, store, event_key=u"alerts", sdk=sdk)
40+
handlers = create_handlers(sdk, AlertExtractor, output_logger, store)
4141
extractor = AlertExtractor(sdk, handlers)
4242
if args.advanced_query:
4343
exit_if_advanced_query_used_with_other_search_args(args)

src/code42cli/cmds/search_shared/extraction.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def verify_begin_date_requirements(args, cursor_store):
3333
exit(1)
3434

3535

36-
def create_handlers(output_logger, cursor_store, event_key, sdk=None):
36+
def create_handlers(sdk, extractor_class, output_logger, cursor_store):
37+
extractor = extractor_class(sdk, ExtractionHandlers())
3738
handlers = ExtractionHandlers()
3839
handlers.TOTAL_EVENTS = 0
3940

@@ -52,19 +53,21 @@ def handle_error(exception):
5253
handlers.get_cursor_position = cursor_store.get_stored_cursor_timestamp
5354

5455
@warn_interrupt(
55-
warning=u"Cancelling operation cleanly to keep checkpoint data accurate. One moment..."
56+
warning=u"Attempting to cancel cleanly to keep checkpoint data accurate. One moment..."
5657
)
5758
def handle_response(response):
5859
response_dict = json.loads(response.text)
59-
events = response_dict.get(event_key)
60-
if event_key == u"alerts":
60+
events = response_dict.get(extractor._key)
61+
if extractor._key == u"alerts":
6162
try:
6263
events = get_alert_details(sdk, events)
6364
except Exception as ex:
6465
handlers.handle_error(ex)
6566
handlers.TOTAL_EVENTS += len(events)
6667
for event in events:
6768
output_logger.info(event)
69+
last_event_timestamp = extractor._get_timestamp_from_item(event)
70+
handlers.record_cursor_position(last_event_timestamp)
6871

6972
handlers.handle_response = handle_response
7073
return handlers

src/code42cli/cmds/securitydata/extraction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def extract(sdk, profile, output_logger, args):
2828
args: Command line args used to build up file event query filters.
2929
"""
3030
store = FileEventCursorStore(profile.name) if args.incremental else None
31-
handlers = create_handlers(output_logger, store, event_key=u"fileEvents")
31+
handlers = create_handlers(sdk, FileEventExtractor, output_logger, store)
3232
extractor = FileEventExtractor(sdk, handlers)
3333
if args.advanced_query:
3434
exit_if_advanced_query_used_with_other_search_args(args)

src/code42cli/logger.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from code42cli.compat import str
77
from code42cli.util import get_user_project_path, is_interactive, color_text_red
88

9+
# prevent loggers from printing stacks to stderr if a pipe is broken
10+
logging.raiseExceptions = False
911

1012
logger_deps_lock = Lock()
1113
ERROR_LOG_FILE_NAME = u"code42_errors.log"

src/code42cli/main.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
from code42cli.cmds.profile import ProfileSubcommandLoader
1616
from code42cli.commands import Command, SubcommandLoader
1717
from code42cli.invoker import CommandInvoker
18+
from code42cli.util import flush_stds_out_err_without_printing_error
1819

1920

2021
# Handle KeyboardInterrupts by just exiting instead of printing out a stack
2122
def exit_on_interrupt(signal, frame):
23+
print()
2224
sys.exit(1)
2325

2426

@@ -118,7 +120,10 @@ def _create_legal_hold_loader(self):
118120
def main():
119121
top = Command(u"", u"", subcommand_loader=MainSubcommandLoader(u""))
120122
invoker = CommandInvoker(top)
121-
invoker.run(sys.argv[1:])
123+
try:
124+
invoker.run(sys.argv[1:])
125+
finally:
126+
flush_stds_out_err_without_printing_error()
122127

123128

124129
if __name__ == u"__main__":

src/code42cli/util.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ def is_interactive():
5050
return sys.stdin.isatty()
5151

5252

53+
def flush_stds_out_err_without_printing_error():
54+
"""Workaround for bug in python3 that causes exception to be printed on broken pipe:
55+
https://bugs.python.org/issue11380
56+
"""
57+
try:
58+
sys.stdout.flush()
59+
except BrokenPipeError:
60+
try:
61+
sys.stdout.close()
62+
except BrokenPipeError:
63+
try:
64+
sys.stderr.flush()
65+
except BrokenPipeError:
66+
sys.stderr.close()
67+
68+
5369
def get_url_parts(url_str):
5470
parts = url_str.split(u":")
5571
port = None
@@ -129,19 +145,20 @@ def my_important_func():
129145

130146
def __init__(self, warning="Cancelling operation cleanly, one moment... "):
131147
self.warning = warning
132-
self.old_handler = None
148+
self.old_int_handler = None
133149
self.interrupted = False
134150
self.exit_instructions = "Hit CTRL-C again to force quit."
135151

136152
def __enter__(self):
137-
self.old_handler = getsignal(SIGINT)
153+
self.old_int_handler = getsignal(SIGINT)
138154
signal(SIGINT, self._handle_interrupts)
139155
return self
140156

141157
def __exit__(self, exc_type, exc_val, exc_tb):
142158
if self.interrupted:
143159
exit(1)
144-
signal(SIGINT, self.old_handler)
160+
signal(SIGINT, self.old_int_handler)
161+
145162
return False
146163

147164
def _handle_interrupts(self, sig, frame):

src/code42cli/worker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from threading import Thread, Lock
2+
from time import sleep
23

34
from py42.exceptions import Py42HTTPError, Py42ForbiddenError
45

@@ -81,7 +82,8 @@ def stats(self):
8182
def wait(self):
8283
"""Wait for the tasks in the queue to complete. This should usually be called before
8384
program termination."""
84-
self._queue.join()
85+
while not self._queue.empty():
86+
sleep(0.5)
8587

8688
def _process_queue(self):
8789
while True:

0 commit comments

Comments
 (0)