docs: @Trivial
      @FFDCIgnore(InterruptedException.class)
      void scheduledScan() {
          // Don't perform a scheduled scan if this monitor holder is paused
          if (isStopped)
              return;
  
          // 152229: Changed this code to get the monitor type locally.  That is, now we save the monitor type in the constructor.
          // We used to get the monitor type here by monitorRef.getProperty(FileMonitor.MONITOR_TYPE)). That caused a
          // ConcurrentModificationException because of interference from the JMocked FileMonitor in the unit test code.
          // Don't do anything if this is an external monitor
          if (FileMonitor.MONITOR_TYPE_EXTERNAL.equals(monitorRef.getProperty(FileMonitor.MONITOR_TYPE))) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(this, tc, "scheduledScan - RETURN early - external monitor");
              }
              return;
          }
  
          // Don't do anything if the framework is stopping. Allow normal component cleanup
          // to deactivate/clean up the scheduled tasks, but make this a no-op if the
          // server is shutting down.
          if (FrameworkState.isStopping()) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(this, tc, "scheduledScan - RETURN early - framework stopping");
              }
              return;
          }
  
          // Don't do anything unless we can set scanInProgress to true
          // Use this to prevent scanning while a scan is in progress. Monitor notification must happen
          // outside of the lock to prevent deadlocks.
          if (!scanInProgress.compareAndSet(false, true)) {
              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                  Tr.debug(this, tc, "scheduledScan - RETURN early - already scan in progress?");
              }
              return;
          }
  
          try {
              Set<File> created = new HashSet<File>();
              Set<File> deleted = new HashSet<File>();
              Set<File> modified = new HashSet<File>();
  
              // Try to obtain the scan lock -- this might fail if the monitor configuration is being updated
              if (scanLock.tryLock()) {
                  try {
                      // Always try destroy when we obtain the lock: it will return true if this is in destroy or destroyed state
                      // Also (after we have tried doDestroy) ensure that we are in active state
                      if (!doDestroy() && (monitorState.get() == MonitorState.ACTIVE.ordinal())) {
                          if (coreService.isDetailedScanTraceEnabled() && TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                              Tr.debug(this, tc, "File monitor scan: begin", updateMonitors);
                          }
  
                          scanForUpdates(created, deleted, modified);
  
                          if (!created.isEmpty() || !modified.isEmpty() || !deleted.isEmpty()) {
                              // Check again, make sure there have been no further changes since the scan we just
                              // ran (we don't want to read the files until any updates are complete, files may be
                              // in process of being copied).
                              // what seems to be the vogue is to do this check to make sure nothing moved twice.
                              // i.e. keep the re-check interval at 100ms, but require two clean go-rounds before
                              // delivering the all clear.
                              boolean oneClean = false;
                              boolean twoClean = false;
  
                              List<File> createdCheck = new ArrayList<File>();
                              List<File> deletedCheck = new ArrayList<File>();
                              List<File> modifiedCheck = new ArrayList<File>();
  
                              do {
                                  // Wait for 100 ms before checking again to give files time to finish
                                  // copying if they are mid copy. Note this may not work for copying
                                  // large files via programs like FTP where the copy may pause or
                                  // if an OS creates the file and sets the size/last modified before
                                  // the copy completes, but it should fix it for smaller files or for the
                                  // test environment where some files are streamed over rather than copied.
                                  try {
                                      // Only used once and not sure it needs to be configurable so didn't create a
                                      // constant for the delay period.
                                      Thread.sleep(TIME_TO_WAIT_FOR_COPY_TO_COMPLETE);
                                  } catch (InterruptedException ex) {
                                  }
  
                                  // Clear the lists, want a clean set rather than appending to existing to check
                                  // if this loop is "update free". Do not clear the deletedCreatedCheck or
                                  // deletedModifiedCheck as these need to track status over multiple loops.
                                  createdCheck.clear();
                                  deletedCheck.clear();
                                  modifiedCheck.clear();
  
                                  scanForUpdates(createdCheck, deletedCheck, modifiedCheck);
                                  resolveChangesForScheduledScan(created, deleted, modified, createdCheck, deletedCheck, modifiedCheck);
  
                                  if (createdCheck.isEmpty() && modifiedCheck.isEmpty() && deletedCheck.isEmpty()) {
                                      // This run was clean-- hooray!
                                      if (oneClean) {
                                          twoClean = true; // <-- loop exit condition
                                      } else {
                                          oneClean = true; // <-- hopefully only one more time through
                                      }
                                  } else {
                                      oneClean = false; // bummer.
                                  }
  
                                  // Keep going until we have two 100ms intervals with no changes
                                  // (AND the runtime/framework is still happy)
                              } while (!twoClean && FrameworkState.isValid());
                          }
                      }
                  } catch (RuntimeException e) {
                      // TODO: MUST CATCH exceptions here (to at least get FFDC)... ick
                  } finally {
                      try {
                          doDestroy(); // always attempt destroy while we hold the lock
                      } finally {
                          scanLock.unlock();
                      }
                  }
  
                  if (!created.isEmpty() || !modified.isEmpty() || !deleted.isEmpty()) {
                      // changes were discovered: trace & call the registered file monitor
                      if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                          Tr.debug(this, tc, "File monitor scan: end; resources changed",
                                   created.size() + " created",
                                   modified.size() + " modified",
                                   deleted.size() + " deleted",
                                   "running=" + FrameworkState.isValid());
                      }
  
                      // Even if we do get into a scan, make sure the framework is still good before we
                      // push the notification of updates-- Avoid propagating change notification
                      // while components that might react to them are being shut down
  
                      if (FrameworkState.isValid()) {
                          try {
                              monitor.onChange(created, modified, deleted);
                              // If the monitor handled the call cleanly, reset our exception count
                              exceptionCount = 0;
                          } catch (RuntimeException e) {
                              // FFDC instrumentation will go here
                              // Catch the exception so it doesn't kill the whole scheduler
  
                              exceptionCount++;
                              Tr.warning(tc, "fileMonitorException", created, modified, deleted, monitor.getClass(), e.getLocalizedMessage());
                              if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                                  Tr.debug(this, tc, "scheduledScan - exceptionCount=" + exceptionCount);
                              }
  
                              // If the monitor has thrown exceptions a few times in a row abandon
                              // monitoring for it
                              if (exceptionCount >= NUMBER_OF_EXCEPTIONS_BEFORE_DISABLING_MONITOR) {
                                  Tr.warning(tc, "fileMonitorDisabled", NUMBER_OF_EXCEPTIONS_BEFORE_DISABLING_MONITOR, monitor.getClass());
                                  // Reset the exceptionCount just in case we get re-enabled by outside forces for some unknown reason
                                  exceptionCount = 0;
                                  destroy();
                              }
  
                          }
                      } else {
                          //no framework, we should try to cleanup.
                          stop();
                      }
                  } else if (coreService.isDetailedScanTraceEnabled() && TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
                      // If super detailed trace is enabled, we trace the begin/end of all file scans
                      Tr.debug(this, tc, "File monitor scan: end; no changes");
                  }
              } // end if tryLock
          } finally {
              scanInProgress.set(false);
          }
      }