@Override
    public void run() {
      /* Used to check whether there were pendingJoins before waiting */
      boolean noPendingJoins = false;
      OUTER: while (!shutDown) {
        SystemFailure.checkFailure();
        try {
          if (!agent.isListening()) {
            shutDown();
          }

          noPendingJoins = agent.pendingJoins.isEmpty();
          if (noPendingJoins && logger.isDebugEnabled()) {
            logger.debug("Pausing as there are no pending joins ... ");
          }

          // if paused OR no pendingJoins then just wait...
          if (paused || noPendingJoins) {
            if (logger.isTraceEnabled(DM_VERBOSE)) {
              logger.trace(DM_VERBOSE, "JoinProcessor is about to wait...");
            }
            synchronized (lock) {
              lock.wait();
            }
          }
          if (logger.isTraceEnabled(DM_VERBOSE)) {
            logger.trace(DM_VERBOSE, "JoinProcessor has woken up...");
          }
          if (paused) {
            continue;
          }

          // if no join was already in process or if aborted, get a new one...
          if (id == null) {
            List pendingJoinsRef = agent.pendingJoins;
            if (logger.isTraceEnabled(DM_VERBOSE)) {
              logger.trace(DM_VERBOSE, "JoinProcessor pendingJoins: {}", pendingJoinsRef.size());
            }
            if (!pendingJoinsRef.isEmpty()) {
              id = (InternalDistributedMember) pendingJoinsRef.get(0);
              if (logger.isTraceEnabled(DM_VERBOSE)) {
                logger.trace(DM_VERBOSE, "JoinProcessor got a membership event for {}", id);
              }
            }
          }
          if (paused) {
            continue;
          }

          // process the join...
          if (id != null) {
            if (logger.isTraceEnabled(DM_VERBOSE)) {
              logger.trace(DM_VERBOSE, "JoinProcessor handling join for {}", id);
            }
            try {
              agent.handleJoined(id);
            } finally {
              id = null;
            }
          }

        } catch (CancelException e) {
          // we're done!
          shutDown = true; // for safety
          break;
        } catch (InterruptedException e) {
          // When this thread is "paused", it is interrupted
          if (logger.isTraceEnabled(DM_VERBOSE)) {
            logger.trace(DM_VERBOSE, "JoinProcessor has been interrupted...");
          }
          if (shutDown) {
            break;
          }
          if (paused || noPendingJoins) {
            /*
             * JoinProcessor waits when: 1. this.paused is set to true 2. There are no pending joins
             *
             * If the JoinProcessor is interrupted when it was waiting due to second reason, it
             * should still continue after catching InterruptedException. From code, currently,
             * JoinProcessor is interrupted through JoinProcessor.abort() method which is called
             * when a member departs as part of cancelPendingJoin().
             */
            if (logger.isDebugEnabled()) {
              logger.debug("JoinProcessor was interrupted when it was paused, now resuming ...", e);
            }
            noPendingJoins = false;
            continue;
          }
          break; // Panic!

        } catch (OperationCancelledException ex) {
          if (logger.isTraceEnabled(DM_VERBOSE)) {
            logger.trace(DM_VERBOSE, "join cancelled by departure");
          }

        } catch (VirtualMachineError err) {
          SystemFailure.initiateFailure(err);
          // If this ever returns, rethrow the error. We're poisoned
          // now, so don't let this thread continue.
          throw err;
        } catch (Throwable e) {
          // Whenever you catch Error or Throwable, you must also
          // catch VirtualMachineError (see above). However, there is
          // _still_ a possibility that you are dealing with a cascading
          // error condition, so you also need to check to see if the JVM
          // is still usable:
          SystemFailure.checkFailure();
          for (Throwable cause = e.getCause(); cause != null; cause = cause.getCause()) {
            if (cause instanceof InterruptedException) {
              if (logger.isTraceEnabled(DM_VERBOSE)) {
                logger.trace(DM_VERBOSE, "JoinProcessor has been interrupted...");
              }
              continue OUTER;
            }
          }
          logger.error("JoinProcessor caught exception...", e);
        }
      }
    }
Processes pending membership joins in a dedicated thread. If we processed the joins in the same
thread as the membership handler, then we run the risk of getting deadlocks and such.
geode-core/src/main/java/org/apache/geode/internal/admin/remote/RemoteGfManagerAgent.java