docs: void completed(Exception throwable) {
          boolean needToFire = true;
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
              Tr.debug(tc, "completed", throwable.getMessage());
          }
          synchronized (this.completedSemaphore) {
              // future can already be completed
              if (this.completed || !this.channel.isOpen()) {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Future completed after already cancelled or socket was closed");
                  }
                  return;
              }
              this.completed = true;
  
              // new timeout code - cancel active timeout request
              if (getTimeoutWorkItem() != null) {
                  getTimeoutWorkItem().state = TimerWorkItem.ENTRY_CANCELLED;
              }
  
              this.exception = throwable;
  
              if (this.firstListener == null) {
                  // Sync Read/Write request.
                  // must do this inside the sync, or else syncRead/Write could complete
                  // before we get here, and we would be doing this on the next
                  // read/write request!
                  needToFire = false;
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "doing notify for future " + this);
                  }
                  this.completedSemaphore.notifyAll();
              }
          } // end of sync
  
          if (needToFire) {
              // ASync Read/Write request.
              // need to do this outside the sync, or else we will hold the sync
              // for the user's callback.
              fireCompletionActions();
          }
  
      }