docs: public static void stopChains(List<String> chains, long timeout, final Runnable runOnStop) {
          if (null == chains || chains.isEmpty()) {
              return;
          }
          final boolean bTrace = TraceComponent.isAnyTracingEnabled();
          final ChannelFramework cf = ChannelFrameworkFactory.getChannelFramework();
  
          final long quiesceTimeout = (-1L == timeout) ? cf.getDefaultChainQuiesceTimeout() : timeout;
  
          final UtilsChainListener listener = new UtilsChainListener();
          for (String chain : chains) {
              ChainData cd = cf.getChain(chain);
              if (null == cd) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Skipping unknown chain; " + chain);
                  }
                  continue;
              }
              if (FlowType.OUTBOUND.equals(cd.getType()) || !cf.isChainRunning(cd)) {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Skipping chain; " + chain);
                  }
                  continue;
              }
              try {
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Stopping chain: " + chain);
                  }
  
                  if (quiesceTimeout > 0 ) {
                      listener.watchChain(cd);
                  }
                  cf.stopChain(cd, quiesceTimeout);
  
              } catch (Throwable t) {
                  // framework already FFDCs on stopChain failure
                  if (bTrace && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Error stopping chain: " + t);
                  }
              }
          }
  
          if (runOnStop != null) {
              ExecutorService executorService = CHFWBundle.getExecutorService();
              if (null != executorService) {
                  // Use a different thread to wait for chain stop and then finish..
                  Runnable runner = new Runnable() {
                      @Override
                      public void run() {
                          // wait for the quiesce time to expire
                          listener.waitOnChains(quiesceTimeout);
  
                          // call the runnable to finish cleanup activity.
                          runOnStop.run();
                      }
                  };
                  executorService.execute(runner);
  
                  // We've forked waiting for chain stop to a different thread
                  return;
              }
          }
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
              Tr.debug(tc, "Waiting for chains to stop");
          }
          // wait for the quiesce time to expire
          listener.waitOnChains(quiesceTimeout);
      }