class_docstr: A slice of a spillable buffer
  
  This buffer applies the slicing and then delegates all
  operations to its owning buffer.
  
  Parameters
  ----------
  owner : SpillableBufferOwner
      The owner of the view
  offset : int
      Memory offset into the owning buffer
  size : int
      Size of the view (in bytes)
class_docstr_tok: ["A","slice","of","a","spillable","buffer","This","buffer","applies","the","slicing","and","then","delegates","all","operations","to","its","owning","buffer",".","Parameters","--","--","--","--","--","owner",":","SpillableBufferOwner","The","owner","of","the","view","offset",":","int","Memory","offset","into","the","owning","buffer","size",":","int","Size","of","the","view","(","in","bytes",")"]
docstr: Serialize the Buffer
  
  Normally, we would use `[self]` as the frames. This would work but
  also mean that `self` becomes exposed permanently if the frames are
  later accessed through `__cuda_array_interface__`, which is exactly
  what libraries like Dask+UCX would do when communicating!
  
  The sound solution is to modify Dask et al. so that they access the
  frames through `.get_ptr()` and holds on to the `spill_lock` until
  the frame has been transferred. However, until this adaptation we
  use a hack where the frame is a `Buffer` with a `spill_lock` as the
  owner, which makes `self` unspillable while the frame is alive but
  doesn't expose `self` when `__cuda_array_interface__` is accessed.
  
  Warning, this hack means that the returned frame must be copied before
  given to `.deserialize()`, otherwise we would have a `Buffer` pointing
  to memory already owned by an existing `SpillableBufferOwner`.
docstr_tok: ["Serialize","the","Buffer","Normally",",","we","would","use","`","[","self","]","`","as","the","frames",".","This","would","work","but","also","mean","that","`","self","`","becomes","exposed","permanently","if","the","frames","are","later","accessed","through","`","__cuda_array_interface__","`",",","which","is","exactly","what","libraries","like","Dask+UCX","would","do","when","communicating","!","The","sound","solution","is","to","modify","Dask","et","al",".","so","that","they","access","the","frames","through","`",".get_ptr","(",")","`","and","holds","on","to","the","`","spill_lock","`","until","the","frame","has","been","transferred",".","However",",","until","this","adaptation","we","use","a","hack","where","the","frame","is","a","`","Buffer","`","with","a","`","spill_lock","`","as","the","owner",",","which","makes","`","self","`","unspillable","while","the","frame","is","alive","but","does","n't","expose","`","self","`","when","`","__cuda_array_interface__","`","is","accessed",".","Warning",",","this","hack","means","that","the","returned","frame","must","be","copied","before","given","to","`",".deserialize","(",")","`",",","otherwise","we","would","have","a","`","Buffer","`","pointing","to","memory","already","owned","by","an","existing","`","SpillableBufferOwner","`","."]
code: def serialize(self) -> Tuple[dict, list]:
      """Serialize the Buffer
  
      Normally, we would use `[self]` as the frames. This would work but
      also mean that `self` becomes exposed permanently if the frames are
      later accessed through `__cuda_array_interface__`, which is exactly
      what libraries like Dask+UCX would do when communicating!
  
      The sound solution is to modify Dask et al. so that they access the
      frames through `.get_ptr()` and holds on to the `spill_lock` until
      the frame has been transferred. However, until this adaptation we
      use a hack where the frame is a `Buffer` with a `spill_lock` as the
      owner, which makes `self` unspillable while the frame is alive but
      doesn't expose `self` when `__cuda_array_interface__` is accessed.
  
      Warning, this hack means that the returned frame must be copied before
      given to `.deserialize()`, otherwise we would have a `Buffer` pointing
      to memory already owned by an existing `SpillableBufferOwner`.
      """
      header: Dict[str, Any] = {}
      frames: List[Buffer | memoryview]
      with self._owner.lock:
          header["type-serialized"] = pickle.dumps(self.__class__)
          header["owner-type-serialized"] = pickle.dumps(
              type(self._owner)
          )
          header["frame_count"] = 1
          if self.is_spilled:
              frames = [self.memoryview()]
          else:
              # TODO: Use `frames=[self]` instead of this hack, see doc above
              spill_lock = SpillLock()
              self.spill_lock(spill_lock)
              ptr, size, _ = self.memory_info()
              frames = [
                  Buffer(
                      owner=BufferOwner._from_device_memory(
                          cuda_array_interface_wrapper(
                              ptr=ptr,
                              size=size,
                              owner=(self._owner, spill_lock),
                          ),
                          exposed=False,
                      )
                  )
              ]
          return header, frames
  
imports: from __future__ import annotations
  import collections.abc
  import pickle
  import time
  import weakref
  from threading import RLock
  from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Tuple
  import numpy
  from typing_extensions import Self
  import rmm
  from cudf.core.buffer.buffer import Buffer, BufferOwner, cuda_array_interface_wrapper, host_memory_allocation
  from cudf.utils.nvtx_annotation import _get_color_for_nvtx, annotate
  from cudf.utils.string import format_bytes
label_desc: Use image node_id 8 for calling the SpillableBuffer obj's underlying member method code with example usage: obj.serialize() and returns: header,  frames