def _RegisterDebuggee(self, service): """Single attempt to register the debuggee. If the registration succeeds, sets self._debuggee_id to the registered debuggee ID. Args: service: client to use for API calls Returns: (registration_required, delay) tuple """ try: request = {'debuggee': self._GetDebuggee()} try: response = service.debuggees().register(body=request).execute() self._debuggee_id = response['debuggee']['id'] native.LogInfo('Debuggee registered successfully, ID: %s' % ( self._debuggee_id)) self.register_backoff.Succeeded() return (False, 0) # Proceed immediately to list active breakpoints. except BaseException: native.LogInfo('Failed to register debuggee: %s, %s' % (request, traceback.format_exc())) except BaseException: native.LogWarning('Debuggee information not available: ' + traceback.format_exc()) return (True, self.register_backoff.Failed())
def _TryActivateBreakpoint(self): """Sets the breakpoint if the module has already been loaded. This function will complete the breakpoint with error if breakpoint definition is incorrect. Examples: invalid line or bad condition. If the code object corresponding to the source path can't be found, this function returns False. In this case, the breakpoint is not completed, since the breakpoint may be deferred. Returns: True if breakpoint was set or false otherwise. False can be returned for potentially deferred breakpoints or in case of a bad breakpoint definition. The self._completed flag distinguishes between the two cases. """ # Find the code object in which the breakpoint is being set. code_object = self._FindCodeObject() if not code_object: return False # Compile the breakpoint condition. condition = None if self.definition.get('condition'): try: condition = compile(self.definition.get('condition'), '<condition_expression>', 'eval') except TypeError as e: # condition string contains null bytes. self._CompleteBreakpoint({ 'status': { 'isError': True, 'refersTo': 'BREAKPOINT_CONDITION', 'description': { 'format': 'Invalid expression', 'parameters': [str(e)]}}}) return False except SyntaxError as e: self._CompleteBreakpoint({ 'status': { 'isError': True, 'refersTo': 'BREAKPOINT_CONDITION', 'description': { 'format': 'Expression could not be compiled: $0', 'parameters': [e.msg]}}}) return False line = self.definition['location']['line'] native.LogInfo('Creating new Python breakpoint %s in %s, line %d' % ( self.GetBreakpointId(), code_object, line)) self._cookie = native.SetConditionalBreakpoint( code_object, line, condition, self._BreakpointEvent) self._RemoveImportHook() return True
def _ListActiveBreakpoints(self, service): """Single attempt query the list of active breakpoints. Must not be called before the debuggee has been registered. If the request fails, this function resets self._debuggee_id, which triggers repeated debuggee registration. Args: service: client to use for API calls Returns: (registration_required, delay) tuple """ try: response = service.debuggees().breakpoints().list( debuggeeId=self._debuggee_id, waitToken=self._wait_token).execute() breakpoints = response.get('breakpoints') or [] self._wait_token = response.get('nextWaitToken') if cmp(self._breakpoints, breakpoints) != 0: self._breakpoints = breakpoints native.LogInfo( 'Breakpoints list changed, %d active, wait token: %s' % (len(self._breakpoints), self._wait_token)) self.on_active_breakpoints_changed( copy.deepcopy(self._breakpoints)) except Exception as e: if not isinstance( e, apiclient.errors.HttpError) or e.resp.status != 409: native.LogInfo('Failed to query active breakpoints: ' + traceback.format_exc()) # Forget debuggee ID to trigger repeated debuggee registration. Once the # registration succeeds, the worker thread will retry this query self._debuggee_id = None return (True, self.list_backoff.Failed()) self.list_backoff.Succeeded() return (False, 0)
def Clear(self): """Clears the breakpoint and releases all breakpoint resources. This function is assumed to be called by BreakpointsManager. Therefore we don't call CompleteBreakpoint from here. """ self._RemoveImportHook() if self._cookie is not None: native.LogInfo('Clearing breakpoint %s' % self.GetBreakpointId()) native.ClearConditionalBreakpoint(self._cookie) self._cookie = None self._completed = True # Never again send updates for this breakpoint.
def _TransmitBreakpointUpdates(self, service): """Tries to send pending breakpoint updates to the backend. Sends all the pending breakpoint updates. In case of transient failures, the breakpoint is inserted back to the top of the queue. Application failures are not retried (for example updating breakpoint in a final state). Each pending breakpoint maintains a retry counter. After repeated transient failures the breakpoint is discarded and dropped from the queue. Args: service: client to use for API calls Returns: (reconnect, timeout) tuple. The first element ("reconnect") is set to true on unexpected HTTP responses. The caller should discard the HTTP connection and create a new one. The second element ("timeout") is set to None if all pending breakpoints were sent successfully. Otherwise returns time interval in seconds to stall before retrying. """ reconnect = False retry_list = [] # There is only one consumer, so two step pop is safe. while self._transmission_queue: breakpoint, retry_count = self._transmission_queue.popleft() try: service.debuggees().breakpoints().update( debuggeeId=self._debuggee_id, id=breakpoint['id'], body={'breakpoint': breakpoint}).execute() native.LogInfo('Breakpoint %s update transmitted successfully' % ( breakpoint['id'])) except apiclient.errors.HttpError as err: # Treat 400 error codes (except timeout) as application error that will # not be retried. All other errors are assumed to be transient. status = err.resp.status is_transient = ((status >= 500) or (status == 408)) if is_transient and retry_count < self.max_transmit_attempts - 1: native.LogInfo('Failed to send breakpoint %s update: %s' % ( breakpoint['id'], traceback.format_exc())) retry_list.append((breakpoint, retry_count + 1)) elif is_transient: native.LogWarning( 'Breakpoint %s retry count exceeded maximum' % breakpoint['id']) else: # This is very common if multiple instances are sending final update # simultaneously. native.LogInfo('%s, breakpoint: %s' % (err, breakpoint['id'])) except Exception: native.LogWarning( 'Fatal error sending breakpoint %s update: %s' % ( breakpoint['id'], traceback.format_exc())) reconnect = True self._transmission_queue.extend(retry_list) if not self._transmission_queue: self.update_backoff.Succeeded() # Nothing to send, wait until next breakpoint update. return (reconnect, None) else: return (reconnect, self.update_backoff.Failed())
def IsValidSourcePath(source_path): """Checks availability of a Python module. This function checks if it is possible that a module will match the specified path. We only use the file name and we ignore the directory. There is no absolutely correct way to do this. The application may just import a module from a string, or dynamically change sys.path. This function implements heuristics that should cover all reasonable cases with a good performance. There can be some edge cases when this code is going to scan a huge number of directories. This can be very expensive. To mitigate it, we limit the number of directories that can be scanned. If this threshold is reached, false negatives are possible. Args: source_path: source path as specified in the breakpoint. Returns: True if it is possible that a module matching source_path will ever be loaded or false otherwise. """ def IsPackage(path): """Checks if the specified directory is a valid Python package.""" init_base_path = os.path.join(path, '__init__.py') return (os.path.isfile(init_base_path) or os.path.isfile(init_base_path + 'c') or os.path.isfile(init_base_path + 'o')) def SubPackages(path): """Gets a list of all the directories of subpackages of path.""" if os.path.isdir(path): for name in os.listdir(path): if '.' in name: continue # This is definitely a file, package names can't have dots. if directory_lookups[0] >= _DIRECTORY_LOOKUP_QUOTA: break directory_lookups[0] += 1 subpath = os.path.join(path, name) if IsPackage(subpath): yield subpath start_time = time.time() directory_lookups = [0] file_name = _GetModuleName(source_path) if not file_name: return False # Recursively discover all the subpackages in all the Python paths. paths = set() pending = set(sys.path) while pending: path = pending.pop() paths.add(path) pending |= frozenset(SubPackages(path)) - paths # Append all directories where some modules have already been loaded. There # is a good chance that the file we are looking for will be there. This is # only useful if a module got somehow loaded outside of sys.path. We don't # include these paths in the recursive discovery of subpackages because it # takes a lot of time in some edge cases and not worth it. default_path = sys.path[0] for unused_module_name, module in sys.modules.copy().iteritems(): file_path = getattr(module, '__file__', None) path, unused_name = os.path.split(file_path) if file_path else (None, None) paths.add(path or default_path) try: imp.find_module(file_name, list(paths)) rc = True except ImportError: rc = False native.LogInfo( ('Look up for %s completed in %d directories, ' 'scanned %d directories (quota: %d), ' 'result: %r, total time: %f ms') % (file_name, len(paths), directory_lookups[0], _DIRECTORY_LOOKUP_QUOTA, rc, (time.time() - start_time) * 1000)) return rc