docs: def send_data(self, stream_id, data, *, end_stream=False):
          """
          Send request or response body on the given stream.
  
          This will block until either whole data is sent, or the stream gets
          closed. Meanwhile, a paused underlying transport or a closed flow
          control window will also help waiting. If the peer increase the flow
          control window, this method will start sending automatically.
  
          This can be called multiple times, but it must be called after a
          `start_request` or `start_response` with the returning stream ID, and
          before any `end_stream` instructions; Otherwise it will fail.
  
          The given data may be automatically split into smaller frames in order
          to fit in the configured frame size or flow control window.
  
          Each stream can only have one `send_data` running, others calling this
          will be blocked on a per-stream lock (wlock), so that coroutines
          sending data concurrently won't mess up with each other.
  
          Similarly, the completion of the call to this method does not mean the
          data is delivered.
  
          :param stream_id: Which stream to send data on
          :param data: Bytes to send
          :param end_stream: To finish sending a request or response, set this to
                             `True` to close the given stream locally after data
                             is sent (default `False`).
          :raise: `SendException` if there is an error sending data. Data left
                  unsent can be found in `data` of the exception.
          """
          try:
              with (yield from self._get_stream(stream_id).wlock):
                  while True:
                      yield from _wait_for_events(
                          self._resumed, self._get_stream(stream_id).window_open)
                      self._priority.unblock(stream_id)
                      waiter = asyncio.Future()
                      if not self._priority_events:
                          self._loop.call_soon(self._priority_step)
                      self._priority_events[stream_id] = waiter
                      try:
                          yield from waiter
                          data_size = len(data)
                          size = min(
                              data_size,
                              self._conn.local_flow_control_window(stream_id),
                              self._conn.max_outbound_frame_size)
                          if data_size == 0 or size == data_size:
                              self._conn.send_data(stream_id, data,
                                                   end_stream=end_stream)
                              self._flush()
                              break
                          elif size > 0:
                              self._conn.send_data(stream_id, data[:size])
                              data = data[size:]
                              self._flush()
                      finally:
                          self._priority_events.pop(stream_id, None)
                          self._priority.block(stream_id)
                          if self._priority_events:
                              self._loop.call_soon(self._priority_step)
          except ProtocolError:
              raise exceptions.SendException(data)