/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.master.file.meta;

import alluxio.Constants;

import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;

import javax.annotation.concurrent.ThreadSafe;

/**
 * A list of non-empty {@link TtlBucket}s sorted by ttl interval start time of each bucket.
 * <p>
 * Two adjacent buckets may not have adjacent intervals since there may be no inodes with ttl value
 * in the skipped intervals.
 */
@ThreadSafe
public final class TtlBucketList {
  /**
   * List of buckets sorted by interval start time. SkipList is used for O(logn) insertion and
   * retrieval, see {@link ConcurrentSkipListSet}.
   */
  private final ConcurrentSkipListSet<TtlBucket> mBucketList;

  /**
   * Creates a new list of {@link TtlBucket}s.
   */
  public TtlBucketList() {
    mBucketList = new ConcurrentSkipListSet<>();
  }

  /**
   * Gets the bucket in the list that contains the inode.
   *
   * @param inode the inode to be contained
   * @return the bucket containing the inode, or null if no such bucket exists
   */
  private TtlBucket getBucketContaining(Inode<?> inode) {
    if (inode.getTtl() == Constants.NO_TTL) {
      // no bucket will contain a inode with NO_TTL.
      return null;
    }

    long ttlEndTimeMs = inode.getCreationTimeMs() + inode.getTtl();
    // Gets the last bucket with interval start time less than or equal to the inode's life end
    // time.
    TtlBucket bucket = mBucketList.floor(new TtlBucket(ttlEndTimeMs));
    if (bucket == null || bucket.getTtlIntervalEndTimeMs() < ttlEndTimeMs
        || (bucket.getTtlIntervalEndTimeMs() == ttlEndTimeMs
            && TtlBucket.getTtlIntervalMs() != 0)) {
      // 1. There is no bucket in the list, or
      // 2. All buckets' interval start time is larger than the inode's life end time, or
      // 3. No bucket actually contains ttlEndTimeMs in its interval.
      return null;
    }

    return bucket;
  }

  /**
   * Inserts an {@link Inode} to the appropriate bucket where its ttl end time lies in the
   * bucket's interval, if no appropriate bucket exists, a new bucket will be created to contain
   * this inode, if ttl value is {@link Constants#NO_TTL}, the inode won't be inserted to any
   * buckets and nothing will happen.
   *
   * @param inode the inode to be inserted
   */
  public void insert(Inode<?> inode) {
    if (inode.getTtl() == Constants.NO_TTL) {
      return;
    }

    TtlBucket bucket = getBucketContaining(inode);
    if (bucket == null) {
      long ttlEndTimeMs = inode.getCreationTimeMs() + inode.getTtl();
      // No bucket contains the inode, so a new bucket should be added with an appropriate interval
      // start. Assume the list of buckets have continuous intervals, and the first interval starts
      // at 0, then ttlEndTimeMs should be in number (ttlEndTimeMs / interval) interval, so the
      // start time of this interval should be (ttlEndTimeMs / interval) * interval.
      long interval = TtlBucket.getTtlIntervalMs();
      bucket = new TtlBucket(interval == 0 ? ttlEndTimeMs : ttlEndTimeMs / interval * interval);
      synchronized (mBucketList) {
        TtlBucket tmpBucket = getBucketContaining(inode);
        if (tmpBucket != null) {
          // The bucket has been inserted in another thread just now, so use the existing bucket.
          bucket = tmpBucket;
        } else {
          mBucketList.add(bucket);
        }
      }
    }
    // TODO(zhouyufa): Consider the concurrent situation that the bucket is expired and processed by
    // the InodeTtlChecker, then adding the inode into the bucket is meaningless since the bucket
    // will not be accessed again. (c.f. ALLUXIO-2821)
    bucket.addInode(inode);
  }

  /**
   * Removes a inode from the bucket containing it if the inode is in one of the buckets, otherwise,
   * do nothing.
   *
   * <p>
   * Assume that no inode in the buckets has ttl value that equals {@link Constants#NO_TTL}.
   * If a inode with valid ttl value is inserted to the buckets and its ttl value is going to be set
   * to {@link Constants#NO_TTL} later, be sure to remove the inode from the buckets first.
   *
   * @param inode the inode to be removed
   */
  public void remove(Inode<?> inode) {
    TtlBucket bucket = getBucketContaining(inode);
    if (bucket != null) {
      bucket.removeInode(inode);
    }
  }

  /**
   * Retrieves buckets whose ttl interval has expired before the specified time, that is, the
   * bucket's interval start time should be less than or equal to (specified time - ttl interval).
   * The returned set is backed by the internal set.
   *
   * @param time the expiration time
   * @return a set of expired buckets or an empty set if no buckets have expired
   */
  public Set<TtlBucket> getExpiredBuckets(long time) {
    return mBucketList.headSet(new TtlBucket(time - TtlBucket.getTtlIntervalMs()), true);
  }

  /**
   * Removes all buckets in the set.
   *
   * @param buckets a set of buckets to be removed
   */
  public void removeBuckets(Set<TtlBucket> buckets) {
    mBucketList.removeAll(buckets);
  }
}

@@ -2901,32 +2901,65 @@
 %0A%0A    TtlBucket 
+bucket;%0A    while (true) %7B%0A      
 bucket = getBuck
@@ -2975,32 +2975,34 @@
 ing(inode);%0A    
+  
 if (bucket == nu
@@ -2992,25 +2992,25 @@
  if (bucket 
-=
+!
 = null) %7B%0A  
@@ -3003,24 +3003,47 @@
  != null) %7B%0A
+        break;%0A      %7D%0A
       long t
@@ -3640,20 +3640,10 @@
     
-synchronized
+if
  (mB
@@ -3655,300 +3655,157 @@
 List
-) %7B%0A        TtlBucket tmpBucket = getBucketContaining(inode);%0A        if (tmpBucket != null) %7B%0A          // The bucket has been inserted in another thread just now, so use the existing bucket.%0A          bucket = tmpBucket;%0A        %7D else %7B%0A          mBucketList.add(bucket);%0A        %7D%0A      %7D
+.add(bucket)) %7B%0A        break;%0A      %7D%0A      // If we reach here, it means the same bucket has been concurrently inserted by another%0A      // thread.
 %0A   

Use lock-free approach to insert a ttlbucket