docs: public Future<Boolean> startModule(ExtendedModuleInfo moduleInfo) throws StateChangeException {
          final WebModuleInfo webModule = (WebModuleInfo) moduleInfo;
          
          // Track number of modules starting. Rarely a server can begin shutting down while a module is still starting which
          // can result in an NPE. By counting modules in start, if there are module still starting during server quiesce the
          // webcontainer can hold server start while modules finish starting.
          modulesStarting++;
          Future<Boolean> result;
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.entry(tc, "startModule: " + webModule.getName() + " " + webModule.getContextRoot() + ", modulesStarting = " + modulesStarting);
          }
          try {
              
              // if this service has been deactivated, then leave as cleanly as possible
              if ((futureMonitor == null) || (self.get() == null)){
                 CompletedFuture f = new CompletedFuture(false);
                 return f;
              }
              // If server is stopping return don't start the application, just an future set to true. 
              if (isServerStopping()) {
                  if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      Tr.debug(tc, "startModule: " + "server is stopping");
                  }
                  // Server is stopping so we don't want to continue but options for return are not great:
                  // - null is not an option, it results in an NPE from the caller.
                  // - a Future with no result : server will wait 30 seconds for the result to be set
                  // - a Future with the result set to False : server will output a W message to say app did not start.
                  // - a Future with the result set to True  : sever will output an I message to say app has started.
                  // Go with the latter, because a W message would cause tests to fail,
                  result = futureMonitor.createFutureWithResult(Boolean.TRUE);
              } else {
                  WebModuleMetaData wmmd = (WebModuleMetaData) ((ExtendedModuleInfo)webModule).getMetaData();
                  J2EEName j2eeName = wmmd.getJ2EEName();
                  WebAppConfiguration appConfig = (WebAppConfiguration) wmmd.getConfiguration();
   
                  WebCollaboratorComponentMetaDataImpl wccmdi = (WebCollaboratorComponentMetaDataImpl) wmmd.getCollaboratorComponentMetaData();
                  wccmdi.setJ2EEName(j2eeName);
                  metaDataService.fireComponentMetaDataCreated(wccmdi);
  
                  WebComponentMetaDataImpl wcmdi = (WebComponentMetaDataImpl) appConfig.getDefaultComponentMetaData();
                  wcmdi.setJ2EEName(j2eeName);
                  metaDataService.fireComponentMetaDataCreated(wcmdi);
  
                  for (Iterator<IServletConfig> servletConfigs = appConfig.getServletInfos(); servletConfigs.hasNext();) {
                      IServletConfig servletConfig = servletConfigs.next();
                      wcmdi = (WebComponentMetaDataImpl) servletConfig.getMetaData();
                      wcmdi.setJ2EEName(j2eeNameFactory.create(j2eeName.getApplication(), j2eeName.getModule(), servletConfig.getName()));
                      metaDataService.fireComponentMetaDataCreated(wcmdi);
                  }
  
                  final ClassLoader moduleLoader = webModule.getClassLoader();
  
                  // The class loader for this application will do two things:
                  // 1. Provide classes needed for the application to run
                  // 2. Provide internal classes that implement third party or spec interfaces and are loaded by factories inspecting the thread context class loader
                  // The class loader on the web module can handle 1 already but we enhance it with some internal classes in order to support 2.
                  // Note that we only have one class loader though as it delegates to the enhanced class loader or app class loader which means that when app classes 
                  // are created they will use the app class loader but when something asks the TCCL for one of the internal class they will go to the enhanced CL delegate
                  final ClassLoadingService cls = classLoadingSRRef.getService();
                  final ClassLoader threadClassLoader = cls.createThreadContextClassLoader(moduleLoader);
                  com.ibm.wsspi.adaptable.module.Container webModuleContainer = webModule.getContainer();
                  final DeployedModule dMod = new DeployedModule(webModuleContainer, appConfig, threadClassLoader);
                  this.deployedModuleMap.put(webModule, dMod);
                  addWebApplication(dMod);
              
                  // If the Webcontainer attribute "deferServletLoad" is set to "false" (not the default)
                  // then start the web application now, inline on this thread
                  // otherwise, launch is on its own thread
                  if (!WCCustomProperties.DEFER_SERVLET_LOAD) {
                      if (!startWebApplication(dMod)) {
                          throw new StateChangeException("startWebApplication");
                      }
                  } else {
                      backgroundWebAppStartFutures.add(es, new Runnable() {
                          @Override
                          public void run() {
                                  startWebApplication(dMod); 
                          }
                      });
                  }
                  registerMBeans((WebModuleMetaDataImpl) wmmd, webModuleContainer);
                  result =  addContextRootRequirement(dMod);
              }
          } catch (Throwable e) {
              FFDCWrapper.processException(e, getClass().getName(), "startModule", new Object[] { webModule, this });
              if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
                  Tr.event(tc, "startModule: " + webModule.getName() + "; " + e);
              }
              
              //PI58875
              this.stopModule(moduleInfo);
              throw new StateChangeException(e);
          } finally {
              modulesStarting--;
          }
          if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
              Tr.exit(tc, "startModule: ", "modulesStarting = " + modulesStarting);
          }
          return result;
      }