1#!/usr/bin/env python3 2 3# Copyright 2007 Google Inc. 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 18# USA. 19 20"""Conservative approximation of include dependencies for C/C++.""" 21 22__author__ = "Nils Klarlund" 23 24# TODO (klarlund) Implement abort mechanism: regularly check whether 25# ppid is 0; if so, then abort. 26 27# Python imports 28import gc 29import getopt 30import glob 31import os 32import re 33import shutil 34import signal 35import socketserver 36import sys 37import tempfile 38import traceback 39 40# Include server imports 41import basics 42import distcc_pump_c_extensions 43import include_analyzer_memoizing_node 44import statistics 45 46# The default size passed to listen by a streaming socket server of 47# socketserver is only 5. Make it 128 (which appears to be the hard 48# built-in limit for Linux). This enables requests to the include 49# server to be buffered better. 50REQUEST_QUEUE_SIZE = 128 51 52Debug = basics.Debug 53DEBUG_TRACE = basics.DEBUG_TRACE 54DEBUG_WARNING = basics.DEBUG_WARNING 55# Exceptions. 56SignalSIGTERM = basics.SignalSIGTERM 57NotCoveredError = basics.NotCoveredError 58NotCoveredTimeOutError = basics.NotCoveredTimeOutError 59 60 61# USAGE 62 63def Usage(): 64 print("""Usage: 65 66include_server --port INCLUDE_SERVER_PORT [OPTIONS] 67 68where INCLUDE_SERVER_PORT is a socket name. Fork the include server 69for incremental include analysis. The include server answers queries 70from the distcc client about which files to include in a C/C++ 71compilation. This command itself terminates as soon as the include 72server has been spawned. 73 74OPTIONS: 75 76 -dPAT, --debug_pattern=PAT Bit vector for turning on warnings and debugging 77 1 = warnings 78 2 = trace some functions 79 other powers of two: see basics.py. 80 81 -e, --email Send email to discc-pump developers when include 82 server gets in trouble. 83 84 --email_bound NUMBER Maximal number of emails to send (in addition to 85 a final email). Default: 3. 86 87 --no-email Do not send email. 88 89 --path_observation_re=RE Issue warning message whenever a filename is 90 resolved to a realpath that is matched by RE, 91 which is a regular expression in Python syntax. 92 This is useful for finding out where files included 93 actually come from. Use RE="" to find them all. 94 Note: warnings must be enabled with at least -d1. 95 96 --pid_file FILEPATH The pid of the include server is written to file 97 FILEPATH. 98 99 -s, --statistics Print information to stdout about include analysis. 100 101 --stat_reset_triggers=LIST Flush stat caches when the timestamp of any 102 filepath in LIST changes or the filepath comes in 103 or out of existence. LIST is a colon separated 104 string of filepaths, possibly containing simple 105 globs (as allowed by Python's glob module). Print 106 a warning whenever such a change happens (if 107 warnings are enabled). This option allows limited 108 exceptions to distcc_pump's normal assumption that 109 source files are not modified during the build. 110 111 -t, --time Print elapsed, user, and system time to stderr. 112 113 --unsafe_absolute_includes Do preprocessing on the compilation server even if 114 includes of absolute filepaths are encountered. 115 Such includes are then ignored for the purposes of 116 gathering the include closure. See the 117 include_server(1) man page for further information. 118 Using this option may lead to incorrect results. 119 120 --no_force_dirs Do not force the creation of all directories used 121 in an include path. May improve performance for 122 some cases, but will break builds which use 123 include structures like "<foo/../file.h>" without 124 including other files in foo/. 125 126 -v, --verify Verify that files in CPP closure are contained in 127 closure calculated by include processor. 128 129 -w, --write_include_closure Write a .d_approx file which lists all the 130 included files calculated by the include server; 131 with -x, additionally write the included files 132 as calculated by CPP to a .d_exact file. 133 134 -x, --exact_analysis Use CPP instead, do not omit system headers files. 135""") 136 137# TODO(klarlund) 138# --simple_algorithm not currently implemented 139 140 141# UTILITIES 142 143def _PrintStackTrace(fd): 144 """Print stacktrace to file object.""" 145 print('------- Include server stack trace -----------', file=fd) 146 # Limit is 1000 entries. 147 traceback.print_exc(1000, fd) 148 print('----------------------------------------------', file=fd) 149 150 151class _EmailSender(object): 152 """For sending emails. We limit their number to avoid email storms.""" 153 154 def __init__(self): 155 self.number_sent = 0 156 157 def TryToSend(self, fd, force=False, never=False): 158 """Send the contents of file to predefined blame address. 159 Arguments: 160 fd: open file descriptor, will remain open 161 force: send even if bound has been reached 162 """ 163 if not basics.opt_send_email: return 164 if self.number_sent >= basics.opt_email_bound and not force: return 165 if never: return 166 self.number_sent += 1 167 # For efficiency, we postpone reading needed libraries for emailing until 168 # now. 169 import smtplib 170 import getpass 171 import socket 172 try: 173 user_addr = "%s@%s" % (getpass.getuser(), socket.gethostname()) 174 fd.seek(0) 175 msg = "Subject: %s\nTo: %s\nFrom: %s\n\n%s\n%s" % ( 176 basics.EMAIL_SUBJECT, 177 basics.DCC_EMAILLOG_WHOM_TO_BLAME, 178 user_addr, 179 "Automated email number %d in include server session.\n" % 180 self.number_sent, 181 fd.read()) 182 s = smtplib.SMTP() 183 s.connect() 184 s.sendmail(user_addr, [basics.DCC_EMAILLOG_WHOM_TO_BLAME], msg) 185 Debug(DEBUG_WARNING, "Include server sent email to %s", 186 basics.DCC_EMAILLOG_WHOM_TO_BLAME) 187 s.close() 188 except: 189 Debug(DEBUG_WARNING, basics.CANT_SEND_MESSAGE) 190 traceback.print_exc() 191 192 def MaybeSendEmail(self, fd, force=False, never=False): 193 """Print warning and maybe send email; the contents is from file object. 194 195 Arguments: 196 fd: a file object that will be closed. 197 force: send the mail even if number of emails sent exceed 198 basics.opt_email_bound 199 """ 200 fd.seek(0, 0) 201 Debug(DEBUG_WARNING, "%s", fd.read()) 202 self.TryToSend(fd, force, never) 203 fd.close() 204 205 206NEWLINE_RE = re.compile(r"\n", re.MULTILINE) 207BACKSLASH_NEWLINE_RE = re.compile(r"\\\n", re.MULTILINE) 208 209 210def ExactDependencies(cmd, realpath_map, systemdir_prefix_cache, 211 translation_unit): 212 """The dependencies as calculated by CPP, the C Preprocessor. 213 Arguments: 214 cmd: the compilation command, a string 215 realpath_map: map from filesystem paths (no symlink) to idx 216 systemdir_prefix_cache: says whether realpath starts with a systemdir 217 translation_unit: string 218 Returns: 219 the set of realpath indices of the include dependencies. 220 Raises: 221 NotCoveredError 222 """ 223 224 # Safely get a couple of temporary files. 225 (fd_o, name_o) = tempfile.mkstemp("distcc-pump") 226 (fd_d, name_d) = tempfile.mkstemp("distcc-pump") 227 228 def _delete_temp_files(): 229 os.close(fd_d) 230 os.close(fd_o) 231 os.unlink(name_o) 232 os.unlink(name_d) 233 234 # Remove -o option and call with -E, -M, and -MF flags. 235 preprocessing_command = ( 236 (re.sub(r"\s-o[ ]?(\w|[./+-])+", " ", cmd) # delete -o option 237 + " -o %(name_o)s" # add it back, but to temp file, 238 + " -E" # macro processing only 239 + " -M -MF %(name_d)s") % # output .d file 240 {'name_o':name_o, 'name_d':name_d}) 241 242 ret = os.system(preprocessing_command) 243 if ret: 244 _delete_temp_files() 245 raise NotCoveredError("Could not execute '%s'" % 246 preprocessing_command, 247 translation_unit) 248 # Using the primitive fd_d file descriptor for reading is cumbersome, so open 249 # normally as well. 250 fd_d_ = open(name_d, "rb", encoding='latin-1') 251 # Massage the contents of fd_d_ 252 dotd = re.sub("^.*:", # remove Makefile target 253 "", 254 NEWLINE_RE.sub( 255 "", # remove newlines 256 BACKSLASH_NEWLINE_RE.sub("", # remove backslashes 257 fd_d_.read()))) 258 fd_d_.close() 259 _delete_temp_files() 260 # The sets of dependencies is a set the of realpath indices of the 261 # absolute filenames corresponding to files in the dotd file. 262 deps = set([ rp_idx 263 for filepath in dotd.split() 264 for rp_idx in [ realpath_map.Index(os.path.join(os.getcwd(), 265 filepath)) ] 266 if not systemdir_prefix_cache.StartsWithSystemdir(rp_idx, 267 realpath_map) 268 ]) 269 statistics.len_exact_closure = len(deps) 270 return deps 271 272 273def WriteDependencies(deps, result_file, realpath_map): 274 """Write the list of deps to result_file. 275 Arguments: 276 deps: a list of realpath indices 277 result_file: a filepath 278 realpath_map: map from filesystem paths (no symlink) to idx 279 """ 280 try: 281 fd = open(result_file, "w") 282 fd.write("\n".join([realpath_map.string[d] for d in deps])) 283 fd.write("\n") 284 fd.close() 285 except (IOError, OSError) as why: 286 raise NotCoveredError("Could not write to '%s': %s" % (result_file, why)) 287 288 289def VerifyExactDependencies(include_closure, 290 exact_no_system_header_dependency_set, 291 realpath_map, 292 translation_unit): 293 """Compare computed and real include closures, ignoring system 294 header files (such as those in /usr/include). 295 Arguments: 296 include_closure: a dictionary whose keys are realpath indices 297 exact_no_system_header_dependency_set: set of realpath indices 298 realpath_map: map from filesystem paths (no symlink) to idx 299 translation_unit: string 300 Raises: 301 NotCoveredError 302""" 303 diff = exact_no_system_header_dependency_set - set(include_closure) 304 statistics.len_surplus_nonsys = ( 305 len(set(include_closure) - exact_no_system_header_dependency_set)) 306 307 if diff != set([]): 308 # Pick one bad dependency. 309 bad_dep = diff.pop() 310 raise NotCoveredError( 311 ("Calculated include closure does not contain: '%s'.\n" 312 + "There %s %d such missing %s.") 313 % (realpath_map.string[bad_dep], 314 len(diff) == 0 and "is" or "are", 315 len(diff) + 1, 316 len(diff) == 0 and "dependency" or "dependencies"), 317 translation_unit) 318 319 320# A SOCKET SERVER 321 322class Queuingsocketserver(socketserver.UnixStreamServer): 323 """A socket server whose request queue have size REQUEST_QUEUE_SIZE.""" 324 request_queue_size = REQUEST_QUEUE_SIZE 325 326 def handle_error(self, _, client_address): 327 """Re-raise current exception; overrides socketserver.handle_error. 328 """ 329 raise 330 331 332# HANDLER FOR SOCKETSERVER 333 334def DistccIncludeHandlerGenerator(include_analyzer): 335 """Wrap a socketserver based on the include_analyzer object inside a new 336 type that is a class named IncludeHandler.""" 337 338 # TODO(klarlund): Can we do this without dynamic type generation? 339 340 class IncludeHandler(socketserver.StreamRequestHandler): 341 """Define a handle() method that invokes the include closure algorithm .""" 342 343 def handle(self): 344 """Using distcc protocol, read command and return include closure. 345 346 Do the following: 347 - Read from the socket, using the RPC protocol of distcc: 348 - the current directory, and 349 - the compilation command, already broken down into an argv vector. 350 - Parse the command to find options like -I, -iquote,... 351 - Invoke the include server's closure algorithm to yield a set of files 352 and set of symbolic links --- both sets of files under client_root, 353 which duplicates the part of the file system that CPP will need. 354 - Transmit the file and link names on the socket using the RPC protocol. 355 """ 356 statistics.StartTiming() 357 currdir = distcc_pump_c_extensions.RCwd(self.rfile.fileno()) 358 cmd = distcc_pump_c_extensions.RArgv(self.rfile.fileno()) 359 360 try: 361 try: 362 # We do timeout the include_analyzer using the crude mechanism of 363 # SIGALRM. This signal is problematic if raised while Python is doing 364 # I/O in the C extensions and during use of the subprocess 365 # module. 366 # 367 # TODO(klarlund) The Python library manual states: "When a signal 368 # arrives during an I/O operation, it is possible that the I/O 369 # operation raises an exception after the signal handler returns. This 370 # is dependent on the underlying Unix system's semantics regarding 371 # interrupted system calls." We must clarify this. Currently, there 372 # is I/O during DoCompilationCommand: 373 # 374 # - when a link is created in mirror_path.py 375 # - module compress_files is used 376 # 377 # TODO(klarlund): Modify mirror_path so that is accumulates symbolic 378 # link operations instead of actually executing them on the spot. The 379 # accumulated operations can be executed after DoCompilationCommand 380 # when the timer has been cancelled. 381 include_analyzer.timer = basics.IncludeAnalyzerTimer() 382 files_and_links = ( 383 include_analyzer. 384 DoCompilationCommand(cmd, currdir, 385 include_analyzer.client_root_keeper)) 386 finally: 387 # The timer should normally be cancelled during normal execution 388 # flow. Still, we want to make sure that this is indeed the case in 389 # all circumstances. 390 include_analyzer.timer.Cancel() 391 392 except NotCoveredError as inst: 393 # Warn user. The 'Preprocessing locally' message is meant to 394 # assure the user that the build process is otherwise intact. 395 fd = tempfile.TemporaryFile(mode='w+') 396 print(("Preprocessing locally. Include server not covering: %s for " 397 + "translation unit '%s'") % ( 398 (inst.args and inst.args[0] or "unknown reason", 399 include_analyzer.translation_unit)), file=fd, end=' ') 400 # We don't include a stack trace here. 401 include_analyzer.email_sender.MaybeSendEmail(fd, 402 never=not inst.send_email) 403 # The empty argv list denotes failure. Communicate this 404 # information back to the distcc client, so that it can fall 405 # back to preprocessing on the client. 406 distcc_pump_c_extensions.XArgv(self.wfile.fileno(), []) 407 if isinstance(inst, NotCoveredTimeOutError): 408 Debug(DEBUG_TRACE, 409 "Clearing caches because of include server timeout.") 410 include_analyzer.ClearStatCaches() 411 412 except SignalSIGTERM: 413 # Normally, we will get this exception when the include server is no 414 # longer needed. But we also handle it here, during the servicing of a 415 # request. See basics.RaiseSignalSIGTERM. 416 Debug(DEBUG_TRACE, "SIGTERM received while handling request.") 417 raise 418 except KeyboardInterrupt: 419 # Propagate to the last-chance exception handler in Main. 420 raise 421 except SystemExit as inst: 422 # When handler tries to exit (by invoking sys.exit, which in turn raises 423 # SystemExit), something is really wrong. Terminate the include 424 # server. But, print out an informative message first. 425 fd = tempfile.TemporaryFile(mode='w+') 426 print(("Preprocessing locally. Include server fatal error: '%s' for " 427 + "translation unit '%s'") % ( 428 (inst.args, include_analyzer.translation_unit)), file=fd, end=' ') 429 _PrintStackTrace(fd) 430 include_analyzer.email_sender.MaybeSendEmail(fd, force=True) 431 distcc_pump_c_extensions.XArgv(self.wfile.fileno(), []) 432 sys.exit("Now terminating include server.") 433 # All other exceptions are trapped here. 434 except Exception as inst: 435 # Internal error. Better be safe than sorry: terminate include 436 # server. But show error to user on stderr. We hope this message will be 437 # reported. 438 fd = tempfile.TemporaryFile(mode='w+') 439 print(("Preprocessing locally. Include server internal error: '%s: %s'" 440 + " for translation unit '%s'") % ( 441 (inst.__class__, inst.args, include_analyzer.translation_unit)), file=fd) 442 _PrintStackTrace(fd) 443 # # Force this email through (if basics.opt_send_email is True), because 444 # # this is the last one and this is an important case to report. 445 include_analyzer.email_sender.MaybeSendEmail(fd, force=True) 446 distcc_pump_c_extensions.XArgv(self.wfile.fileno(), []) 447 raise SignalSIGTERM # to be caught in Main with no further stack trace 448 else: 449 # No exception raised, include closure can be trusted. 450 distcc_pump_c_extensions.XArgv(self.wfile.fileno(), files_and_links) 451 # Print out observed paths. 452 if basics.opt_path_observation_re: 453 include_analyzer.build_stat_cache.WarnAboutPathObservations( 454 include_analyzer.translation_unit) 455 # Finally, stop the clock and report statistics if needed. 456 statistics.EndTiming() 457 if basics.opt_statistics: 458 statistics.PrintStatistics(include_analyzer) 459 460 return IncludeHandler 461 462 463def _ParseCommandLineOptions(): 464 """Parse arguments and options for the include server command. 465 466 Returns: 467 (include_server_port, pid_file), where include_server_port 468 is a string and pid_file is a string or None 469 Modifies: 470 option variables in module basics 471 """ 472 try: 473 opts, args = getopt.getopt(sys.argv[1:], 474 "d:estvwx", 475 ["port=", 476 "pid_file=", 477 "debug_pattern=", 478 "email", 479 "no-email", 480 "email_bound=", 481 "exact_analysis", 482 "path_observation_re=", 483 "stat_reset_triggers=", 484 "simple_algorithm", 485 "statistics", 486 "time", 487 "unsafe_absolute_includes", 488 "no_force_dirs", 489 "verify", 490 "write_include_closure"]) 491 except getopt.GetoptError: 492 # Print help information and exit. 493 Usage() 494 sys.exit(1) 495 pid_file = None 496 include_server_port = None 497 for opt, arg in opts: 498 try: 499 if opt in ("-d", "--debug_pattern"): 500 basics.opt_debug_pattern = int(arg) 501 if opt in ("--port", ): 502 include_server_port = arg 503 if opt in ("--pid_file",): 504 pid_file = arg 505 if opt in ("-e", "--email"): 506 basics.opt_send_email = True 507 if opt in ("--no-email",): 508 basics.opt_send_email = False 509 if opt in ("--email_bound",): 510 basics.opt_email_bound = int(arg) 511 if opt in ("--path_observation_re",): 512 basics.opt_path_observation_re = re.compile(arg) 513 if opt in ("--stat_reset_triggers",): 514 basics.opt_stat_reset_triggers = ( 515 dict([ (glob_expr, 516 dict ([ (path, basics.Stamp(path)) 517 for path in glob.glob(glob_expr) ])) 518 for glob_expr in arg.split(':') ])) 519 if opt in ("--simple_algorithm",): 520 basics.opt_simple_algorithm = True 521 sys.exit("Not implemented") 522 if opt in ("--unsafe_absolute_includes",): 523 basics.opt_unsafe_absolute_includes = True 524 if opt in ("--no_force_dirs",): 525 basics.opt_no_force_dirs = True 526 if opt in ("-s", "--statistics"): 527 basics.opt_statistics = True 528 if opt in ("-t", "--time"): 529 basics.opt_print_times = True 530 if opt in ("-v", "--verify"): 531 basics.opt_verify = True 532 if opt in ("-w", "--write_include_closure"): 533 basics.opt_write_include_closure = True 534 if opt in ("-x", "--exact_analysis"): 535 basics.opt_exact_include_analysis = True 536 except ValueError: 537 Usage() 538 sys.exit(1) 539 # We must have a port! 540 if not include_server_port: 541 print("INCLUDE_SERVER_PORT not provided. Aborting.", file=sys.stderr) 542 print("-------------------------------------------", "\n", file=sys.stderr) 543 Usage() 544 sys.exit(1) 545 return (include_server_port, pid_file) 546 547 548def _PrintTimes(times_at_start, times_at_fork, times_child): 549 """Print elapsed, user, system, and user + system times.""" 550 # The os.times format stores user time in positions 0 and 2 (for parent and 551 # children, resp.) Similarly, system time is stored in positions 1 and 552 # 3. Elapsed time is in position 4. Elapsed time is measured relative to some 553 # epoch whereas user and system time are 0 at the time of process creation. 554 total_u = (times_at_fork[0] + times_at_fork[2] 555 + times_child[0] + times_child[2]) 556 total_s = (times_at_fork[1] + times_at_fork[3] 557 + times_child[1] + times_child[1]) 558 total_cpu = total_u + total_s 559 total_e = times_child[4] - times_at_start[4] 560 print("Include server timing. ", sys.stderr) 561 print("Elapsed: %3.1fs User: %3.1fs System: %3.1fs User + System: %3.1fs" % 562 (total_e, total_u, total_s, total_cpu), file=sys.stderr) 563 564 565class _IncludeServerPortReady(object): 566 """A simple semaphore for forked processes. 567 568 The implementation uses an unnamed pipe.""" 569 570 def __init__(self): 571 """Constructor. 572 573 Should be called before fork. 574 """ 575 (self.read_fd, self.write_fd) = os.pipe() 576 577 def Acquire(self): 578 """Acquire the semaphore after fork; blocks until a call of Release.""" 579 if os.read(self.read_fd, 1) != b'\n': 580 sys.exit("Include server: _IncludeServerPortReady.Acquire failed.") 581 582 def Release(self): 583 """Release the semaphore after fork.""" 584 if os.write(self.write_fd, b'\n') != 1: 585 sys.exit("Include server: _IncludeServerPortReady.Release failed.") 586 587 588def _SetUp(include_server_port): 589 """Setup include_analyzer and socket server. 590 591 Returns: (include_analyzer, server)""" 592 593 try: 594 os.unlink(include_server_port) 595 except (IOError, OSError): 596 pass # this would be expected, the port provided should not exist 597 598 if os.sep != '/': 599 sys.exit("Expected '/' as separator in filepaths.") 600 601 client_root_keeper = basics.ClientRootKeeper() 602 # Clean out any junk left over from prior runs. 603 client_root_keeper.CleanOutOthers() 604 605 Debug(DEBUG_TRACE, "Starting socketserver %s" % include_server_port) 606 607 # Create the analyser. 608 include_analyzer = ( 609 include_analyzer_memoizing_node.IncludeAnalyzerMemoizingNode( 610 client_root_keeper, 611 basics.opt_stat_reset_triggers)) 612 include_analyzer.email_sender = _EmailSender() 613 614 # Wrap it inside a handler that is a part of a UnixStreamServer. 615 server = Queuingsocketserver( 616 include_server_port, 617 # Now, produce a StreamRequestHandler subclass whose new objects has 618 # a handler which calls the include_analyzer just made. 619 DistccIncludeHandlerGenerator(include_analyzer)) 620 621 return (include_analyzer, server) 622 623 624def _CleanOut(include_analyzer, include_server_port): 625 """Prepare shutdown by cleaning out files and unlinking port.""" 626 if include_analyzer and include_analyzer.client_root_keeper: 627 include_analyzer.client_root_keeper.CleanOutClientRoots() 628 try: 629 os.unlink(include_server_port) 630 except OSError: 631 pass 632 633 634def Main(): 635 """Parse command line, fork, and start stream request handler.""" 636 # Remember the time spent in the parent. 637 times_at_start = os.times() 638 include_server_port, pid_file = _ParseCommandLineOptions() 639 # Get locking mechanism. 640 include_server_port_ready = _IncludeServerPortReady() 641 # Now spawn child so that parent can exit immediately after writing 642 # the process id of child to the pid file. 643 times_at_fork = os.times() 644 pid = os.fork() 645 if pid != 0: 646 # In parent. 647 # 648 if pid_file: 649 pid_file_fd = open(pid_file, "w") 650 print(pid, file=pid_file_fd) 651 pid_file_fd.close() 652 # Just run to completion now -- after making sure that child is ready. 653 include_server_port_ready.Acquire() 654 # concerned. 655 else: 656 # In child. 657 # 658 # We call _Setup only now, because the process id, used in naming the client 659 # root, must be that of this process, not that of the parent process. See 660 # _CleanOutOthers for the importance of the process id. 661 (include_analyzer, server) = _SetUp(include_server_port) 662 include_server_port_ready.Release() 663 try: 664 try: 665 gc.set_threshold(basics.GC_THRESHOLD) 666 # Use commented-out line below to have a message printed for each 667 # collection. 668 # gc.set_debug(gc.DEBUG_STATS + gc.DEBUG_COLLECTABLE) 669 server.serve_forever() 670 except KeyboardInterrupt: 671 print("Include server: keyboard interrupt, quitting after cleaning up.", 672 file=sys.stderr) 673 _CleanOut(include_analyzer, include_server_port) 674 except SignalSIGTERM: 675 Debug(DEBUG_TRACE, "Include server shutting down.") 676 _CleanOut(include_analyzer, include_server_port) 677 except: 678 print("Include server: exception occurred, quitting after cleaning up.", 679 file=sys.stderr) 680 _PrintStackTrace(sys.stderr) 681 _CleanOut(include_analyzer, include_server_port) 682 raise # reraise exception 683 finally: 684 if basics.opt_print_times: 685 _PrintTimes(times_at_start, times_at_fork, os.times()) 686 687 688if __name__ == "__main__": 689 # Treat SIGTERM (the default of kill) as Ctrl-C. 690 signal.signal(signal.SIGTERM, basics.RaiseSignalSIGTERM) 691 Main() 692