001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.util.concurrent;
018
019import java.util.PriorityQueue;
020import java.util.concurrent.Executor;
021import java.util.concurrent.TimeUnit;
022import java.util.concurrent.atomic.AtomicLong;
023import java.util.concurrent.locks.Condition;
024import java.util.concurrent.locks.Lock;
025import java.util.concurrent.locks.ReentrantLock;
026import java.util.function.Consumer;
027
028public class AsyncCompletionService<V> {
029
030    private final Executor executor;
031    private final boolean ordered;
032    private final PriorityQueue<Task> queue = new PriorityQueue<>();
033    private final AtomicLong nextId = new AtomicLong();
034    private final AtomicLong index = new AtomicLong();
035    private final ReentrantLock lock;
036    private final Condition available;
037
038    public AsyncCompletionService(Executor executor, boolean ordered) {
039        this(executor, ordered, null);
040    }
041
042    public AsyncCompletionService(Executor executor, boolean ordered, ReentrantLock lock) {
043        this.executor = executor;
044        this.ordered = ordered;
045        this.lock = lock != null ? lock : new ReentrantLock();
046        this.available = this.lock.newCondition();
047    }
048
049    public ReentrantLock getLock() {
050        return lock;
051    }
052
053    public void submit(Consumer<Consumer<V>> runner) {
054        Task f = new Task(nextId.getAndIncrement(), runner);
055        this.executor.execute(f);
056    }
057
058    public void skip() {
059        index.incrementAndGet();
060    }
061
062    public V pollUnordered() {
063        final ReentrantLock lock = this.lock;
064        lock.lock();
065        try {
066            Task t = queue.poll();
067            return t != null ? t.result : null;
068        } finally {
069            lock.unlock();
070        }
071    }
072
073    public V poll() {
074        final ReentrantLock lock = this.lock;
075        lock.lock();
076        try {
077            Task t = queue.peek();
078            if (t != null && (!ordered || index.compareAndSet(t.id, t.id + 1))) {
079                queue.poll();
080                return t.result;
081            } else {
082                return null;
083            }
084        } finally {
085            lock.unlock();
086        }
087    }
088
089    public V poll(long timeout, TimeUnit unit) throws InterruptedException {
090        long nanos = unit.toNanos(timeout);
091        final ReentrantLock lock = this.lock;
092        lock.lockInterruptibly();
093        try {
094            for (;;) {
095                Task t = queue.peek();
096                if (t != null && (!ordered || index.compareAndSet(t.id, t.id + 1))) {
097                    queue.poll();
098                    return t.result;
099                }
100                if (nanos <= 0) {
101                    return null;
102                } else {
103                    nanos = available.awaitNanos(nanos);
104                }
105            }
106        } finally {
107            lock.unlock();
108        }
109    }
110
111    public V take() throws InterruptedException {
112        final ReentrantLock lock = this.lock;
113        lock.lockInterruptibly();
114        try {
115            for (;;) {
116                Task t = queue.peek();
117                if (t != null && (!ordered || index.compareAndSet(t.id, t.id + 1))) {
118                    queue.poll();
119                    return t.result;
120                }
121                available.await();
122            }
123        } finally {
124            lock.unlock();
125        }
126    }
127
128    private void complete(Task task) {
129        final ReentrantLock lock = this.lock;
130        lock.lock();
131        try {
132            queue.add(task);
133            available.signalAll();
134        } finally {
135            lock.unlock();
136        }
137    }
138
139    private class Task implements Runnable, Comparable<Task> {
140        private final long id;
141        private final Consumer<Consumer<V>> runner;
142        private V result;
143
144        Task(long id, Consumer<Consumer<V>> runner) {
145            this.id = id;
146            this.runner = runner;
147        }
148
149        @Override
150        public void run() {
151            runner.accept(this::setResult);
152        }
153
154        protected void setResult(V result) {
155            this.result = result;
156            complete(this);
157        }
158
159        public int compareTo(Task other) {
160            return Long.compare(this.id, other.id);
161        }
162
163        public String toString() {
164            return "SubmitOrderedFutureTask[" + this.id + "]";
165        }
166    }
167}