/**
   * flush state to the given target
   *
   * @param recipients The members who may be making state changes to the region. This is typically
   *     taken from a CacheDistributionAdvisor membership set
   * @param target The member who should have all state flushed to it
   * @param processorType The execution processor type for the marker message that is sent to all
   *     members using the given region
   * @param flushNewOps normally only ops that were started before region profile exchange are
   *     flushed. Setting this to true causes the flush to wait for any started after the profile
   *     exchange as well.
   * @throws InterruptedException If the operation is interrupted, usually for shutdown, an
   *     InterruptedException will be thrown
   * @return true if the state was flushed, false if not
   */
  public boolean flush(
      Set recipients, DistributedMember target, int processorType, boolean flushNewOps)
      throws InterruptedException {

    Set recips = recipients; // do not use recipients parameter past this point
    if (Thread.interrupted()) {
      throw new InterruptedException();
    }

    InternalDistributedMember myId = this.dm.getDistributionManagerId();

    if (!recips.contains(target) && !myId.equals(target)) {
      recips = new HashSet(recipients);
      recips.add(target);
    }
    // partial fix for bug 38773 - ensures that this cache will get both
    // a cache op and an adjunct message when creating a bucket region
    //    if (recips.size() < 2 && !myId.equals(target)) {
    //      return true; // no state to flush to a single holder of the region
    //    }
    StateMarkerMessage smm = new StateMarkerMessage();
    smm.relayRecipient = target;
    smm.processorType = processorType;
    smm.flushNewOps = flushNewOps;
    if (region == null) {
      smm.allRegions = true;
    } else {
      smm.regionPath = region.getFullPath();
    }
    smm.setRecipients(recips);

    StateFlushReplyProcessor gfprocessor = new StateFlushReplyProcessor(dm, recips, target);
    smm.processorId = gfprocessor.getProcessorId();
    if (region != null
        && region.isUsedForPartitionedRegionBucket()
        && region.getDistributionConfig().getAckSevereAlertThreshold() > 0) {
      smm.severeAlertEnabled = true;
      gfprocessor.enableSevereAlertProcessing();
    }
    if (logger.isTraceEnabled(LogMarker.STATE_FLUSH_OP)) {
      logger.trace(LogMarker.STATE_FLUSH_OP, "Sending {} with processor {}", smm, gfprocessor);
    }
    Set failures = this.dm.putOutgoing(smm);
    if (failures != null) {
      if (failures.contains(target)) {
        if (logger.isTraceEnabled(LogMarker.STATE_FLUSH_OP)) {
          logger.trace(
              LogMarker.STATE_FLUSH_OP,
              "failed to send StateMarkerMessage to target {}; returning from flush without waiting for replies",
              target);
        }
        return false;
      }
      gfprocessor.messageNotSentTo(failures);
    }

    try {
      //      try { Thread.sleep(100); } catch (InterruptedException e) {
      // Thread.currentThread().interrupt(); } // DEBUGGING - stall before getting membership to
      // increase odds that target has left
      gfprocessor.waitForReplies();
      if (logger.isTraceEnabled(LogMarker.STATE_FLUSH_OP)) {
        logger.trace(LogMarker.STATE_FLUSH_OP, "Finished processing {}", smm);
      }
    } catch (ReplyException re) {
      logger.warn(
          LocalizedMessage.create(
              LocalizedStrings.StateFlushOperation_STATE_FLUSH_TERMINATED_WITH_EXCEPTION),
          re);
      return false;
    }
    return true;
  }