docs: synchronized void stopChainInternal(Chain chain, long millisec) throws ChannelException, ChainException {
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "stopChainInternal: " + chain.getName() + ", time=" + millisec);
          }
  
          RuntimeState chainState = chain.getState();
          Channel[] chainChannels = chain.getChannels();
          if ((RuntimeState.STARTED == chainState) || (RuntimeState.QUIESCED == chainState)) {
              // Check if this is just a quiesce
              if (millisec > 0L) {
                  if (RuntimeState.QUIESCED == chainState) {
                      // Due to timing windows (i.e. multiple channel providers going away), there is
                      // a race to stop chains. This is not a notable condition: the chain can only be stopped
                      // once.
                      if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                          Tr.exit(tc, "stopChain " + chain.getName(), "chain already quiesced");
                      }
                      return;
                  }
  
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Quiescing chain: " + chain.getName());
                  }
  
                  int i = 0;
                  try {
                      // In start state. Just a quiesce. Iterate the channels and send a
                      // stop warning
                      for (i = 0; i < chainChannels.length; ++i) {
                          // Only alert channels that are active only for this chain.
                          // Otherwise a future stop will never take affect anyhow
                          if (getNumStartedChainsUsingChannel(chainChannels[i].getName()) == 1) {
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "Quiescing channel: " + chainChannels[i].getName());
                              }
                              chainChannels[i].stop(millisec);
                              // Update the state of the channel.
                              setChannelState(chainChannels[i].getName(), RuntimeState.QUIESCED);
                          }
                      }
                      // Start the timer for when the stop will be asserted.
                      StopChainTask task = new StopChainTask(chain.getName(), this);
                      chain.setStopTask(task);
                      // Update the state of the chain.
                      chain.quiesce();
                      this.stopTimer.schedule(task, millisec);
                  } catch (ChannelException e) {
                      FFDCFilter.processException(e, getClass().getName() + ".stopChainInternal", "2711", this, new Object[] { chain, this });
                      Tr.error(tc, "chain.stop.error", new Object[] { chain.getName(), e.toString() });
                      // Need to undo channels stopped so far.
                      for (int j = 0; j < i; j++) {
                          chainChannels[j].start();
                          // Update the state of the channel.
                          setChannelState(chainChannels[j].getName(), RuntimeState.STARTED);
                      }
                      throw e;
                  }
              } else {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Stopping chain: " + chain.getName());
                  }
  
                  if (RuntimeState.QUIESCED == chainState) {
                      // A stop is being requested while a stop task is in place. Need to
                      // cancel it first.
                      StopChainTask task = chain.getStopTask();
                      if (task != null) {
                          task.cancel();
                          chain.setStopTask(null);
                      }
                  }
                  // Disable the channels from the device side up to the application side.
                  List<Channel> channelsToStop = new ArrayList<Channel>(chainChannels.length);
                  for (int i = 0; i < chainChannels.length; ++i) {
                      // Only stop a channel if it is in use by just this chain.
                      if (getNumStartedChainsUsingChannel(chainChannels[i].getName()) <= 1) {
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Placing channel in list to stop: " + chainChannels[i].getName());
                          }
                          if (chain.getChainData().getType().equals(FlowType.OUTBOUND)) {
                              channelsToStop.add(chainChannels[i]);
                          } else if (disableChannelInChain(chainChannels[i], chain)) {
                              channelsToStop.add(chainChannels[i]);
                          }
                      }
                  }
                  int i = 0;
                  try {
                      // Stop the channels from the application side to the device side.
                      for (i = channelsToStop.size() - 1; i >= 0; i--) {
                          stopChannel(channelsToStop.get(i));
                      }
                      chain.stop();
                  } catch (ChannelException e) {
                      FFDCFilter.processException(e, getClass().getName() + ".stopChainInternal", "2763", this, new Object[] { chain, this });
                      Tr.error(tc, "chain.stop.error", new Object[] { chain.getName(), e.toString() });
                      // Need to restart channels that were stopped thus far.
                      if (FlowType.INBOUND.equals(chain.getChainData().getType())) {
                          // Re-enable the channel
                          ((InboundChain) chain).setupDiscProcess();
                      }
                      for (int j = 0; j < i; j++) {
                          startChannelInChain(channelsToStop.get(j), chain);
                      }
                      // Alert the caller.
                      throw e;
                  }
              }
          } else if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
              // Due to timing windows (i.e. multiple channel providers going away), there is
              // a race to stop chains. This is not a notable condition: the chain can only be stopped
              // once.
              Tr.debug(tc, "stopChainInternal " + chain.getName(), "chain is not running");
          }
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "stopChainInternal");
          }
      }