All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.suro.queue.FileBlockingQueue Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013 Netflix, Inc.
 *
 *    Licensed 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.
 */

package com.netflix.suro.queue;

import com.google.common.base.Preconditions;
import com.leansoft.bigqueue.BigArrayImpl;
import com.leansoft.bigqueue.IBigArray;
import com.leansoft.bigqueue.page.IMappedPage;
import com.leansoft.bigqueue.page.IMappedPageFactory;
import com.leansoft.bigqueue.page.MappedPageFactoryImpl;
import com.netflix.suro.message.SerDe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * File based blocking queue with BigQueue
 * @param  Type name should be given and its SerDe should be implemented.
 *
 * With the argument path and name, files will be created under the directory
 * [path]/[name]. BigQueue needs to do garge collection, which is deleting
 * unnecessary page file. Garbage collection is done in the background every
 * gcPeriodInSec seconds.
 *
 * When the messages are retrieved from the queue,
 * we can control the behavior whether to remove messages immediately or wait
 * until we commit. autoCommit true means removing messages immediately.
 *
 * @author jbae
 */
public class FileBlockingQueue extends AbstractQueue implements BlockingQueue {
    private static final Logger log = LoggerFactory.getLogger(FileBlockingQueue.class);

    private final Lock lock = new ReentrantLock();
    private final Condition notEmpty = lock.newCondition();

    final IBigArray innerArray;
    // 2 ^ 3 = 8
    final static int QUEUE_FRONT_INDEX_ITEM_LENGTH_BITS = 3;
    // size in bytes of queue front index page
    final static int QUEUE_FRONT_INDEX_PAGE_SIZE = 1 << QUEUE_FRONT_INDEX_ITEM_LENGTH_BITS;
    // only use the first page
    static final long QUEUE_FRONT_PAGE_INDEX = 0;

    // folder name for queue front index page
    final static String QUEUE_FRONT_INDEX_PAGE_FOLDER = "front_index";

    // front index of the big queue,
    final AtomicLong queueFrontIndex = new AtomicLong();

    // factory for queue front index page management(acquire, release, cache)
    IMappedPageFactory queueFrontIndexPageFactory;

    private final SerDe serDe;
    private final long sizeLimit;

    public FileBlockingQueue(
            String path,
            String name,
            int gcPeriodInSec,
            SerDe serDe) throws IOException {
        this(path, name, gcPeriodInSec, serDe, Long.MAX_VALUE);
    }

    public FileBlockingQueue(
            String path,
            String name,
            int gcPeriodInSec,
            SerDe serDe,
            long sizeLimit) throws IOException {
        innerArray = new BigArrayImpl(path, name);
        // the ttl does not matter here since queue front index page is always cached
        this.queueFrontIndexPageFactory = new MappedPageFactoryImpl(QUEUE_FRONT_INDEX_PAGE_SIZE,
                ((BigArrayImpl)innerArray).getArrayDirectory() + QUEUE_FRONT_INDEX_PAGE_FOLDER,
                10 * 1000/*does not matter*/);
        IMappedPage queueFrontIndexPage = this.queueFrontIndexPageFactory.acquirePage(QUEUE_FRONT_PAGE_INDEX);

        ByteBuffer queueFrontIndexBuffer = queueFrontIndexPage.getLocal(0);
        queueFrontIndex.set(queueFrontIndexBuffer.getLong());

        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    gc();
                } catch (Exception e) {
                    log.error("Exception while gc: " + e.getMessage(), e);
                }
            }
        }, gcPeriodInSec, gcPeriodInSec, TimeUnit.SECONDS);

        this.serDe = serDe;
        this.sizeLimit = sizeLimit;
    }

    public void gc() throws IOException {
        long beforeIndex = queueFrontIndex.get();
        if (beforeIndex > 0L) { // wrap
            beforeIndex--;
            try {
                innerArray.removeBeforeIndex(beforeIndex);
            } catch (IndexOutOfBoundsException e) {
                // do nothing
            }
        }

        try {
            innerArray.limitBackFileSize(sizeLimit);
            if (innerArray.getTailIndex() > queueFrontIndex.get()) {
                queueFrontIndex.set(innerArray.getTailIndex());
            }
        } catch (IndexOutOfBoundsException e) {
            // do nothing
        }
    }

    private void commitInternal() throws IOException {
        // persist the queue front
        IMappedPage queueFrontIndexPage = null;
        queueFrontIndexPage = this.queueFrontIndexPageFactory.acquirePage(QUEUE_FRONT_PAGE_INDEX);
        ByteBuffer queueFrontIndexBuffer = queueFrontIndexPage.getLocal(0);
        queueFrontIndexBuffer.putLong(queueFrontIndex.get());
        queueFrontIndexPage.setDirty(true);
    }

    public void close() {
        try {
            gc();
            if (queueFrontIndexPageFactory != null) {
                queueFrontIndexPageFactory.releaseCachedPages();
            }

            innerArray.close();
        } catch(IOException e) {
            log.error("IOException while closing: " + e.getMessage(), e);
        }
    }

    @Override
    public E poll() {
        if (isEmpty()) {
            return null;
        }
        E x = null;
        lock.lock();
        try {
            if (!isEmpty()) {
                x = consumeElement();
                if (!isEmpty()) {
                    notEmpty.signal();
                }
            }
        } catch (IOException e) {
            log.error("IOException while poll: " + e.getMessage(), e);
            return null;
        } finally {
            lock.unlock();
        }

        return x;
    }

    @Override
    public E peek() {
        if (isEmpty()) {
            return null;
        }
        lock.lock();
        try {
            return consumeElement();
        } catch (IOException e) {
            log.error("IOException while peek: " + e.getMessage(), e);
            return null;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean offer(E e) {
        Preconditions.checkNotNull(e);
        try {
            innerArray.append(serDe.serialize(e));
            signalNotEmpty();
            return true;
        } catch (IOException ex) {
            return false;
        }
    }

    @Override
    public void put(E e) throws InterruptedException {
        offer(e);
    }

    @Override
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        return offer(e);
    }

    @Override
    public E take() throws InterruptedException {
        E x;
        lock.lockInterruptibly();
        try {
            while (isEmpty()) {
                notEmpty.await();
            }
            x = consumeElement();
        } catch (IOException e) {
            log.error("IOException on take: " + e.getMessage(), e);
            return null;
        } finally {
            lock.unlock();
        }

        return x;
    }

    private E consumeElement() throws IOException {
        // restore consumedIndex if not committed
        E x = serDe.deserialize(innerArray.get(queueFrontIndex.getAndIncrement()));
        commitInternal();
        return x;
    }

    @Override
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E x = null;
        long nanos = unit.toNanos(timeout);
        lock.lockInterruptibly();
        try {
            while (isEmpty()) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            x = consumeElement();
        } catch (IOException e) {
            log.error("IOException on poll: " + e.getMessage(), e);
            return null;
        } finally {
            lock.unlock();
        }

        return x;
    }

    @Override
    public int remainingCapacity() {
        return Integer.MAX_VALUE;
    }

    @Override
    public int drainTo(Collection c) {
        return drainTo(c, Integer.MAX_VALUE);
    }

    @Override
    public int drainTo(Collection c, int maxElements) {
        if (c == null)
            throw new NullPointerException();
        if (c == this)
            throw new IllegalArgumentException();

        lock.lock();
        // restore consumedIndex if not committed
        try {
            int n = Math.min(maxElements, size());
            // count.get provides visibility to first n Nodes
            int i = 0;
            while (i < n && queueFrontIndex.get() < innerArray.getHeadIndex()) {
                c.add(serDe.deserialize(innerArray.get(queueFrontIndex.getAndIncrement())));
                ++i;
            }
            commitInternal();
            return n;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public Iterator iterator() {
        return new Iterator() {

            @Override
            public boolean hasNext() {
                return !isEmpty();
            }

            @Override
            public E next() {
                try {
                    E x = consumeElement();
                    return x;
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("remove is not supported, use dequeue()");
            }
        };
    }

    @Override
    public int size() {
        return (int)(innerArray.getHeadIndex() - queueFrontIndex.get());
    }

    private void signalNotEmpty() {
        lock.lock();
        try {
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy