code: @Override
    public void close() throws IOException {
      if (closed.getAndSet(true)) {
        // already closed
        LOG.debug("Ignoring close() as stream is already closed");
        return;
      }
      COSDataBlocks.DataBlock block = getActiveBlock();
      boolean hasBlock = hasActiveBlock();
      LOG.debug("{}: Closing block #{}: current block= {}", this, blockCount,
          hasBlock ? block : "(none)");
      try {
        if (multiPartUpload == null) {
          if (hasBlock) {
            // no uploads of data have taken place, put the single block up.
            // This must happen even if there is no data, so that 0 byte files
            // are created.
            putObject();
          }
        } else {
          // there has already been at least one block scheduled for upload;
          // put up the current then wait
          if (hasBlock && block.hasData()) {
            // send last part
            uploadCurrentBlock();
          }
          // wait for the partial uploads to finish
          final List<PartETag> partETags = multiPartUpload.waitForAllPartUploads();
          // then complete the operation
          multiPartUpload.complete(partETags);
        }
        LOG.debug("Upload complete for {}", writeOperationHelper);
      } catch (IOException ioe) {
        writeOperationHelper.writeFailed(ioe);
        throw ioe;
      } finally {
        closeAll(LOG, block, blockFactory);
        closeAll(LOG);
        clearActiveBlock();
      }
      // All end of write operations, including deleting fake parent directories
      writeOperationHelper.writeSuccessful();
    }
docs: Close the stream.
  
  This will not return until the upload is complete or the attempt to perform
  the upload has failed. Exceptions raised in this method are indicative that
  the write has failed and data is at risk of being lost.
  
  @throws IOException on any failure