docs: def process_frames(self):
          '''
          Process the input buffer.
          '''
          while len(self._frame_buffer):
              # It would make sense to call next_frame, but it's
              # technically faster to repeat the code here.
              frame = self._frame_buffer.popleft()
  
              if self._emergency_close_pending:
                  # Implement stability rule from AMQP 0.9.1 section 1.5.2.5.
                  # Method channel.close: "After sending this method, any
                  # received methods except Close and Close-OK MUST be discarded."
                  #
                  # NOTE: presently, we limit our implementation of the rule to
                  # the "emergency close" scenario to avoid potential adverse
                  # side-effect during normal user-initiated close
                  if (not isinstance(frame, MethodFrame) or
                        frame.class_id != self.channel.CLASS_ID or
                        frame.method_id not in (self.channel.CLOSE_METHOD_ID,
                                                self.channel.CLOSE_OK_METHOD_ID)):
                      self.logger.warn("Emergency channel close: dropping input "
                                       "frame %.255s", frame)
                      continue
              try:
                  self.dispatch(frame)
              except ProtocolClass.FrameUnderflow:
                  return
              except (ConnectionClosed, ChannelClosed):
                  # Immediately raise if connection or channel is closed
                  raise
              except Exception:
                  self.logger.exception(
                      "Closing on failed dispatch of frame %.255s", frame)
  
                  # Spec says that channel should be closed if there's a framing
                  # error. Unsure if we can send close if the current exception
                  # is transport level (e.g. gevent.GreenletExit)
                  self._emergency_close_pending = True
  
                  # Preserve the original exception and traceback during cleanup,
                  # only allowing system-exiting exceptions (e.g., SystemExit,
                  # KeyboardInterrupt) to override it
                  try:
                      raise
                  finally:
                      try:
                          self.close(500, "Failed to dispatch %s" % (str(frame)))
                      except Exception:
                          # Suppress secondary non-system-exiting exception in
                          # favor of the original exception
                          self.logger.exception("Channel close failed")
                          pass