@Override
 @VisibleForTesting
 public void forcePersistNow(boolean full, PersistenceExceptionHandler exceptionHandler) {
   if (full) {
     BrooklynMementoRawData memento =
         BrooklynPersistenceUtils.newStateMemento(managementContext, MementoCopyMode.LOCAL);
     if (exceptionHandler == null) {
       exceptionHandler = persistenceRealChangeListener.getExceptionHandler();
     }
     persistenceStoreAccess.checkpoint(memento, exceptionHandler);
   } else {
     if (!persistenceRealChangeListener.persistNowSafely()) {
       throw new IllegalStateException("Forced persistence failed; see logs fore more detail");
     }
   }
 }
  @Override
  public void setPersister(
      BrooklynMementoPersister val, PersistenceExceptionHandler exceptionHandler) {
    if (persistenceStoreAccess != null && persistenceStoreAccess != val) {
      throw new IllegalStateException(
          "Dynamically changing persister is not supported: old="
              + persistenceStoreAccess
              + "; new="
              + val);
    }
    if (persistenceRealChangeListener != null) {
      // TODO should probably throw here, but previously we have not -- so let's log for now to be
      // sure it's not happening
      LOG.warn(
          "Persister reset after listeners have been set",
          new Throwable("Source of persister reset"));
    }

    this.persistenceStoreAccess = checkNotNull(val, "persister");

    this.persistenceRealChangeListener =
        new PeriodicDeltaChangeListener(
            managementContext.getServerExecutionContext(),
            persistenceStoreAccess,
            exceptionHandler,
            persistMetrics,
            periodicPersistPeriod);
    this.persistencePublicChangeListener = new SafeChangeListener(persistenceRealChangeListener);

    if (persistenceRunning) {
      persistenceRealChangeListener.start();
    }
  }
 @Override
 @VisibleForTesting
 public void waitForPendingComplete(Duration timeout, boolean canTrigger)
     throws InterruptedException, TimeoutException {
   if (persistenceStoreAccess == null || !persistenceRunning) return;
   persistenceRealChangeListener.waitForPendingComplete(timeout, canTrigger);
   persistenceStoreAccess.waitForWritesCompleted(timeout);
 }
 @Override
 public void stopPersistence() {
   LOG.debug(
       "Stopping persistence (" + this + "), mgmt " + managementContext.getManagementNodeId());
   persistenceRunning = false;
   if (persistenceRealChangeListener != null) persistenceRealChangeListener.stop();
   if (persistenceStoreAccess != null) persistenceStoreAccess.disableWriteAccess(true);
   LOG.debug("Stopped rebind (persistence), mgmt " + managementContext.getManagementNodeId());
 }
 @Override
 public void startPersistence() {
   if (readOnlyRunning) {
     throw new IllegalStateException(
         "Cannot start read-only when already running with persistence");
   }
   LOG.debug(
       "Starting persistence (" + this + "), mgmt " + managementContext.getManagementNodeId());
   if (!persistenceRunning) {
     if (managementContext
         .getBrooklynProperties()
         .getConfig(BrooklynServerConfig.PERSISTENCE_BACKUPS_REQUIRED_ON_PROMOTION)) {
       BrooklynPersistenceUtils.createBackup(
           managementContext, CreateBackupMode.PROMOTION, MementoCopyMode.REMOTE);
     }
   }
   persistenceRunning = true;
   readOnlyRebindCount.set(Integer.MIN_VALUE);
   persistenceStoreAccess.enableWriteAccess();
   if (persistenceRealChangeListener != null) persistenceRealChangeListener.start();
 }
  @SuppressWarnings("unchecked")
  @Override
  public void startReadOnly(final ManagementNodeState mode) {
    if (!ManagementNodeState.isHotProxy(mode)) {
      throw new IllegalStateException(
          "Read-only rebind thread only permitted for hot proxy modes; not " + mode);
    }

    if (persistenceRunning) {
      throw new IllegalStateException(
          "Cannot start read-only when already running with persistence");
    }
    if (readOnlyRunning || readOnlyTask != null) {
      LOG.warn(
          "Cannot request read-only mode for "
              + this
              + " when already running - "
              + readOnlyTask
              + "; ignoring");
      return;
    }
    LOG.debug(
        "Starting read-only rebinding ("
            + this
            + "), mgmt "
            + managementContext.getManagementNodeId());

    if (persistenceRealChangeListener != null) persistenceRealChangeListener.stop();
    if (persistenceStoreAccess != null) persistenceStoreAccess.disableWriteAccess(true);

    readOnlyRunning = true;
    readOnlyRebindCount.set(0);

    try {
      rebind(null, null, mode);
    } catch (Exception e) {
      throw Exceptions.propagate(e);
    }

    Callable<Task<?>> taskFactory =
        new Callable<Task<?>>() {
          @Override
          public Task<Void> call() {
            return Tasks.<Void>builder()
                .dynamic(false)
                .displayName("rebind (periodic run")
                .body(
                    new Callable<Void>() {
                      public Void call() {
                        try {
                          rebind(null, null, mode);
                          return null;
                        } catch (RuntimeInterruptedException e) {
                          LOG.debug("Interrupted rebinding (re-interrupting): " + e);
                          if (LOG.isTraceEnabled())
                            LOG.trace("Interrupted rebinding (re-interrupting), details: " + e, e);
                          Thread.currentThread().interrupt();
                          return null;
                        } catch (Exception e) {
                          // Don't rethrow: the behaviour of executionManager is different from a
                          // scheduledExecutorService,
                          // if we throw an exception, then our task will never get executed again
                          if (!readOnlyRunning) {
                            LOG.debug(
                                "Problem rebinding (read-only running has probably just been turned off): "
                                    + e);
                            if (LOG.isTraceEnabled()) {
                              LOG.trace(
                                  "Problem rebinding (read-only running has probably just been turned off), details: "
                                      + e,
                                  e);
                            }
                          } else {
                            LOG.error("Problem rebinding: " + Exceptions.collapseText(e), e);
                          }
                          return null;
                        } catch (Throwable t) {
                          LOG.warn("Problem rebinding (rethrowing)", t);
                          throw Exceptions.propagate(t);
                        }
                      }
                    })
                .build();
          }
        };
    readOnlyTask =
        (ScheduledTask)
            managementContext
                .getServerExecutionContext()
                .submit(
                    new ScheduledTask(
                            MutableMap.of("displayName", "Periodic read-only rebind"), taskFactory)
                        .period(periodicPersistPeriod));
  }