/* * (C) Copyright 2013-2014 Nuxeo SA (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-2.1.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * Benoit Delbosc * Florent Guillaume */ package org.nuxeo.ecm.core.work; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import org.nuxeo.ecm.core.work.api.Work; /** * Memory-based {@link BlockingQueue}. * <p> * In addition, this implementation also keeps a set of {@link Work} ids in the * queue when the queue elements are {@link WorkHolder}s. */ public class MemoryBlockingQueue extends NuxeoBlockingQueue { /** * A {@link LinkedBlockingQueue} that blocks on {@link #offer} and prevents * starvation deadlocks on reentrant calls. */ private static class ReentrantLinkedBlockingQueue<T> extends LinkedBlockingQueue<T> { private static final long serialVersionUID = 1L; private final ReentrantLock limitedPutLock = new ReentrantLock(); private final int limitedCapacity; /** * Creates a {@link LinkedBlockingQueue} with a maximum capacity. * <p> * If the capacity is -1 then this is treated as a regular unbounded * {@link LinkedBlockingQueue}. * * @param capacity the capacity, or -1 for unbounded */ public ReentrantLinkedBlockingQueue(int capacity) { // Allocate more space to prevent starvation dead lock // because a worker can add a new job to the queue. super(capacity < 0 ? Integer.MAX_VALUE : (2 * capacity)); limitedCapacity = capacity; } /** * Block until there are enough remaining capacity to put the entry. */ public void limitedPut(T e) throws InterruptedException { limitedPutLock.lockInterruptibly(); try { while (remainingCapacity() < limitedCapacity) { // TODO replace by wakeup when an element is removed Thread.sleep(100); } put(e); } finally { limitedPutLock.unlock(); } } @Override public boolean offer(T e) { if (limitedCapacity < 0) { return super.offer(e); } // turn non-blocking offer into a blocking put try { if (Thread.currentThread().getName().startsWith( WorkManagerImpl.THREAD_PREFIX)) { // use the full queue capacity for reentrant call put(e); } else { // put only if there are enough remaining capacity limitedPut(e); } return true; } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException("interrupted", ie); } } } protected final MemoryWorkQueuing queuing; protected final BlockingQueue<Runnable> queue; // @GuardedBy("itself") protected final Set<String> workIds; /** * Creates a {@link BlockingQueue} with a maximum capacity. * <p> * If the capacity is -1 then this is treated as a regular unbounded * {@link LinkedBlockingQueue}. * * @param capacity the capacity, or -1 for unbounded */ public MemoryBlockingQueue(MemoryWorkQueuing queuing, int capacity) { this.queuing = queuing; queue = new ReentrantLinkedBlockingQueue<Runnable>(capacity); workIds = new HashSet<String>(); } /** * Checks if the queue contains a given work id. * * @param workId the work id * @return {@code true} if the queue contains the work id */ public boolean containsWorkId(String workId) { synchronized (workIds) { return workIds.contains(workId); } } private Runnable addWorkId(Runnable r) { if (r instanceof WorkHolder) { WorkHolder wh = (WorkHolder) r; String id = WorkHolder.getWork(wh).getId(); synchronized (workIds) { workIds.add(id); } } return r; } private Runnable removeWorkId(Runnable r) { if (r instanceof WorkHolder) { WorkHolder wh = (WorkHolder) r; String id = WorkHolder.getWork(wh).getId(); synchronized (workIds) { workIds.remove(id); } } return r; } @Override public int getQueueSize() { return queue.size(); } @Override public void putElement(Runnable r) throws InterruptedException { queue.put(r); addWorkId(r); } @Override public Runnable pollElement() { Runnable r = queue.poll(); removeWorkId(r); return r; } @Override public Runnable take() throws InterruptedException { Runnable r = queue.take(); removeWorkId(r); return r; } /* * We can implement iterator, super doesn't have it. */ @Override public Iterator<Runnable> iterator() { return new Itr(queue.iterator()); } private class Itr implements Iterator<Runnable> { private Iterator<Runnable> it; private Runnable last; public Itr(Iterator<Runnable> it) { this.it = it; } @Override public boolean hasNext() { return it.hasNext(); } @Override public Runnable next() { return last = it.next(); } @Override public void remove() { it.remove(); removeWorkId(last); } } @Override public Runnable poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); nanos = awaitActivation(nanos); if (nanos <= 0) { return null; } return queue.poll(nanos, TimeUnit.NANOSECONDS); } }