docs: public synchronized void startChainInternal(ChainData targetChainData, ChainStartMode startMode) throws ChannelException, ChainException {
          String chainName = targetChainData.getName();
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "startChainInternal: " + chainName);
          }
          if (!targetChainData.isEnabled()) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                  Tr.event(tc, "Chain " + chainName + " is disabled");
              }
              if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
                  Tr.exit(tc, "startChainInternal");
              }
              return;
          }
          ChainData chainData;
          // Find the chain in the framework configuration.
          Chain chain = getRunningChain(chainName);
          if (null == chain) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Chain not found running.  Double check it is configured.");
              }
              // Get the chain configuration
              chainData = chainDataMap.get(chainName);
              if (null == chainData) {
                  // Did not find the chain in the config. This method must have been
                  // called from something other than startChain (which does this check).
                  // As of 7/20/04 the only other location is the ChainStartAlarmListener.
                  // This is now a case where the framework must have been shut
                  // down while an alarm was set to start the chain later. The alarm will
                  // handle this exception.
                  throw new InvalidChainNameException("Unable to start unknown chain, " + chainName);
              }
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Chain exists, but not in runtime yet.  Initialize it.");
              }
              initChainInternal(chainData);
              chain = getRunningChain(chainName);
              if (null == chain) {
                  // This shouldn't happen.
                  InvalidChainNameException e = new InvalidChainNameException("Unable to start unknown chain, " + chainName);
                  throw e;
              }
          }
  
          // Update the chainData to include what's in the runtime (children channel data, not parent)
          // Note that the input came from a user who only knows about parent channel data.
          chainData = chain.getChainData();
  
          List<Channel> chainsDone = new ArrayList<Channel>();
          try {
              RuntimeState chainState = chain.getState();
              if (RuntimeState.INITIALIZED.equals(chainState)) {
                  // Check for inbound vs outbound. Channels are started in different
                  // orders based on this.
                  if (chainData.getType().equals(FlowType.INBOUND)) {
                      // Inbound chain. Ensure the disc process of each channel is
                      // initialized.
                      ((InboundChain) chain).setupDiscProcess();
                      Channel[] chainChannels = chain.getChannels();
                      ChannelData[] channelData = chain.getChannelsData();
                      // Loop through the channels starting from the app channel and down to
                      // the dev channel.
                      for (int i = chainChannels.length - 1; i >= 0; --i) {
                          if (startChannelInChain(chainChannels[i], chain)) {
                              chainsDone.add(chainChannels[i]);
                              // Only take the next step if the current channel is not the
                              // device
                              // side channel.
                              if (i != 0) {
                                  // Start the disc process between
                                  ((InboundChain) chain).startDiscProcessBetweenChannels((InboundChannel) chainChannels[i], (InboundChannel) chainChannels[i - 1],
                                                                                         channelData[i].getDiscriminatorWeight());
                              }
                          } else {
                              // The channel was not started. I must already be running. No more
                              // work to do.
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(tc, "Channel was already started; " + chainChannels[i].getName());
                              }
                              break;
                          }
                      }
                  } else {
                      // Outbound chain.
                      Channel[] chainChannels = chain.getChannels();
                      // Loop through the channel starts from the app side to the dev side.
                      for (int i = 0; i < chainChannels.length; ++i) {
                          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(tc, "Start channel in chain: " + chainChannels[i].getName());
                          }
                          // Start the channels.
                          if (startChannelInChain(chainChannels[i], chain)) {
                              chainsDone.add(chainChannels[i]);
                          }
                      }
                  }
              } else if (!RuntimeState.STARTED.equals(chainState)) {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "Cannot start chain " + chainData.getName() + ", state: " + chainState.ordinal);
                  }
                  InvalidRuntimeStateException e = new InvalidRuntimeStateException("Cannot start chain " + chainData.getName());
                  throw e;
              }
          } catch (ChannelException e) {
              // Don't ffdc or log error messages in cases where a try will take place
              if (!(e instanceof RetryableChannelException) || (startMode != ChainStartMode.RETRY_EACH_ON_FAIL)) {
                  FFDCFilter.processException(e, getClass().getName() + ".startChainInternal", "2577", this, new Object[] { chainData });
                  ((ChainDataImpl) chainData).chainStartFailed(1, 0);
                  Tr.error(tc, "chain.start.error", new Object[] { chain.getName(), e.toString() });
              } else if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(tc, "Caught RetryableException");
              }
              // Handle partially created chain. Undo any starts that have been done
              // thus far.
              for (int i = 0; i < chainsDone.size(); i++) {
                  try {
                      // Note, the former channel state may have been quiesced. Stopping
                      // anyhow.
                      stopChannel(chainsDone.get(i));
                  } catch (Exception e1) {
                      FFDCFilter.processException(e, getClass().getName() + ".startChainInternal", "2589", this, new Object[] { chainsDone.get(i) });
                  }
              }
              // Throw the exception up to the caller.
              throw e;
          } catch (ChainException e) {
              FFDCFilter.processException(e, getClass().getName() + ".startChainInternal", "2595", this, new Object[] { chainData });
              Tr.error(tc, "chain.start.error", new Object[] { chain.getName(), e.toString() });
              // Handle partially created chain. Undo any starts that have been done
              // thus far.
              for (int i = 0; i < chainsDone.size(); i++) {
                  try {
                      stopChannel(chainsDone.get(i));
                  } catch (Exception e1) {
                      FFDCFilter.processException(e, getClass().getName() + ".startChainInternal", "2602", this, new Object[] { chainsDone.get(i) });
                  }
              }
              // Throw the exception up to the caller.
              throw e;
          }
  
          chain.start();
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "startChainInternal");
          }
      }