code: public Val end(Val returning) {
      sanity_check_refs(returning);
      // Remove all temp frames
      Futures fs = new Futures();
      for (Frame fr : FRAMES.values()) {
        fs = downRefCnt(fr, fs);   // Remove internal Vecs one by one
        DKV.remove(fr._key, fs);   // Shallow remove, internal Vecs removed 1-by-1
      }
      fs.blockForPending();
      FRAMES.clear();             // No more temp frames
      // Copy (as needed) so the returning Frame is completely independent of the
      // (disappearing) session.
      if (returning != null && returning.isFrame()) {
        Frame fr = returning.getFrame();
        Key<Vec>[] vecs = fr.keys();
        for (int i = 0; i < vecs.length; i++) {
          _addRefCnt(vecs[i], -1); // Returning frame has refcnt +1, lower it now; should go to zero internal refcnts.
          if (GLOBALS.contains(vecs[i])) // Copy if shared with globals
            fr.replace(i, vecs[i].get().makeCopy());
        }
      }
      GLOBALS.clear();            // No longer tracking globals
      sanity_check_refs(null);
      REFCNTS.clear();
      return returning;
    }
docs: Normal session exit.  Returned Frames are fully deep-copied, and are responsibility of the caller to delete.
  Returned Frames have their refcnts currently up by 1 (for the returned value itself).