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
Exemplo n.º 3
0
    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