content: package org.apache.solr.update;
  
  /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  import java.util.concurrent.Executors;
  import java.util.concurrent.ScheduledExecutorService;
  import java.util.concurrent.ScheduledFuture;
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.concurrent.atomic.AtomicLong;
  
  import org.apache.solr.common.SolrException;
  import org.apache.solr.common.params.ModifiableSolrParams;
  import org.apache.solr.core.SolrCore;
  import org.apache.solr.request.LocalSolrQueryRequest;
  import org.apache.solr.request.SolrQueryRequest;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * Helper class for tracking autoCommit state.
   * 
   * Note: This is purely an implementation detail of autoCommit and will
   * definitely change in the future, so the interface should not be relied-upon
   * 
   * Note: all access must be synchronized.
   */
  final class CommitTracker implements Runnable {
    protected final static Logger log = LoggerFactory.getLogger(CommitTracker.class);
    
    // scheduler delay for maxDoc-triggered autocommits
    public final int DOC_COMMIT_DELAY_MS = 1;
    
    // settings, not final so we can change them in testing
    private int docsUpperBound;
    private long timeUpperBound;
    
    private final ScheduledExecutorService scheduler = Executors
        .newScheduledThreadPool(1);
    private ScheduledFuture pending;
    
    // state
    private AtomicLong docsSinceCommit = new AtomicLong(0);
    private AtomicInteger autoCommitCount = new AtomicInteger(0);
  
    private final SolrCore core;
  
    private final boolean softCommit;
    private final boolean waitSearcher;
  
    private String name;
    
    public CommitTracker(String name, SolrCore core, int docsUpperBound, int timeUpperBound, boolean waitSearcher, boolean softCommit) {
      this.core = core;
      this.name = name;
      pending = null;
      
      this.docsUpperBound = docsUpperBound;
      this.timeUpperBound = timeUpperBound;
      
      this.softCommit = softCommit;
      this.waitSearcher = waitSearcher;
  
      SolrCore.log.info(name + " AutoCommit: " + this);
    }
    
    public synchronized void close() {
      if (pending != null) {
        pending.cancel(true);
        pending = null;
      }
      scheduler.shutdownNow();
    }
    
    /** schedule individual commits */
    public void scheduleCommitWithin(long commitMaxTime) {
      _scheduleCommitWithin(commitMaxTime);
    }
  
    private void _scheduleCommitWithin(long commitMaxTime) {
      if (commitMaxTime <= 0) return;
      synchronized (this) {
        if (pending != null && pending.getDelay(TimeUnit.MILLISECONDS) <= commitMaxTime) {
          // There is already a pending commit that will happen first, so
          // nothing else to do here.
          // log.info("###returning since getDelay()==" + pending.getDelay(TimeUnit.MILLISECONDS) + " less than " + commitMaxTime);
  
          return;
        }
  
        if (pending != null) {
          // we need to schedule a commit to happen sooner than the existing one,
          // so lets try to cancel the existing one first.
          boolean canceled = pending.cancel(false);
          if (!canceled) {
            // It looks like we can't cancel... it must have just started running!
            // this is possible due to thread scheduling delays and a low commitMaxTime.
            // Nothing else to do since we obviously can't schedule our commit *before*
            // the one that just started running (or has just completed).
            // log.info("###returning since cancel failed");
            return;
          }
        }
  
        // log.info("###scheduling for " + commitMaxTime);
  
        // schedule our new commit
        pending = scheduler.schedule(this, commitMaxTime, TimeUnit.MILLISECONDS);
      }
    }
    
    /**
     * Indicate that documents have been added
     */
    public void addedDocument(int commitWithin) {
      // maxDocs-triggered autoCommit.  Use == instead of > so we only trigger once on the way up
      if (docsUpperBound > 0) {
        long docs = docsSinceCommit.incrementAndGet();
        if (docs == docsUpperBound + 1) {
          // reset the count here instead of run() so we don't miss other documents being added
          docsSinceCommit.set(0);
          _scheduleCommitWithin(DOC_COMMIT_DELAY_MS);
        }
      }
      
      // maxTime-triggered autoCommit
      long ctime = (commitWithin > 0) ? commitWithin : timeUpperBound;
  
      if (ctime > 0) {
        _scheduleCommitWithin(ctime);
      }
    }
    
    /** Inform tracker that a commit has occurred */
    public void didCommit() {
    }
    
    /** Inform tracker that a rollback has occurred, cancel any pending commits */
    public void didRollback() {
      synchronized (this) {
        if (pending != null) {
          pending.cancel(false);
          pending = null; // let it start another one
        }
        docsSinceCommit.set(0);
      }
    }
    
    /** This is the worker part for the ScheduledFuture **/
    public void run() {
      synchronized (this) {
        // log.info("###start commit. pending=null");
        pending = null;  // allow a new commit to be scheduled
      }
  
      SolrQueryRequest req = new LocalSolrQueryRequest(core,
          new ModifiableSolrParams());
      try {
        CommitUpdateCommand command = new CommitUpdateCommand(false);
        command.waitSearcher = waitSearcher;
        // no need for command.maxOptimizeSegments = 1; since it is not optimizing
  
        // we increment this *before* calling commit because it was causing a race
        // in the tests (the new searcher was registered and the test proceeded
        // to check the commit count before we had incremented it.)
        autoCommitCount.incrementAndGet();
  
        core.getUpdateHandler().commit(command);
      } catch (Exception e) {
        SolrException.log(log, "auto commit error...", e);
      } finally {
        // log.info("###done committing");
        req.close();
      }
    }
    
    // to facilitate testing: blocks if called during commit
    public int getCommitCount() {
      return autoCommitCount.get();
    }
    
    @Override
    public String toString() {
      if (timeUpperBound > 0 || docsUpperBound > 0) {
        return (timeUpperBound > 0 ? ("if uncommited for " + timeUpperBound + "ms; ")
            : "")
            + (docsUpperBound > 0 ? ("if " + docsUpperBound + " uncommited docs ")
                : "");
        
      } else {
        return "disabled";
      }
    }
  
    public long getTimeUpperBound() {
      return timeUpperBound;
    }
  
    int getDocsUpperBound() {
      return docsUpperBound;
    }
  
    void setDocsUpperBound(int docsUpperBound) {
      this.docsUpperBound = docsUpperBound;
    }
  
    void setTimeUpperBound(long timeUpperBound) {
      this.timeUpperBound = timeUpperBound;
    }
  }
  