/*
* 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;
while (true) {
bucket = getBucketContaining(inode);
if (bucket != null) {
break;
}
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);
if (mBucketList.add(bucket)) {
break;
}
// If we reach here, it means the same bucket has been concurrently inserted by another
// thread.
}
// 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);
}
}