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.concurrent.Callable;
020import java.util.concurrent.CompletionService;
021import java.util.concurrent.DelayQueue;
022import java.util.concurrent.Delayed;
023import java.util.concurrent.Executor;
024import java.util.concurrent.Future;
025import java.util.concurrent.FutureTask;
026import java.util.concurrent.TimeUnit;
027import java.util.concurrent.atomic.AtomicInteger;
028
029/**
030 * A {@link java.util.concurrent.CompletionService} that orders the completed tasks in the same order as they where
031 * submitted.
032 *
033 * @deprecated use {@link AsyncCompletionService}
034 */
035@Deprecated
036public class SubmitOrderedCompletionService<V> implements CompletionService<V> {
037
038    private final Executor executor;
039
040    // the idea to order the completed task in the same order as they where submitted is to leverage
041    // the delay queue. With the delay queue we can control the order by the getDelay and compareTo methods
042    // where we can order the tasks in the same order as they where submitted.
043    private final DelayQueue<SubmitOrderFutureTask> completionQueue = new DelayQueue<>();
044
045    // id is the unique id that determines the order in which tasks was submitted (incrementing)
046    private final AtomicInteger id = new AtomicInteger();
047    // index is the index of the next id that should expire and thus be ready to take from the delayed queue
048    private final AtomicInteger index = new AtomicInteger();
049
050    private class SubmitOrderFutureTask extends FutureTask<V> implements Delayed {
051
052        // the id this task was assigned
053        private final long id;
054
055        SubmitOrderFutureTask(long id, Callable<V> voidCallable) {
056            super(voidCallable);
057            this.id = id;
058        }
059
060        SubmitOrderFutureTask(long id, Runnable runnable, V result) {
061            super(runnable, result);
062            this.id = id;
063        }
064
065        @Override
066        public long getDelay(TimeUnit unit) {
067            // if the answer is 0 then this task is ready to be taken
068            long answer = id - index.get();
069            if (answer <= 0) {
070                return answer;
071            }
072            // okay this task is not ready yet, and we don't really know when it would be
073            // so we have to return a delay value of one time unit
074            if (TimeUnit.NANOSECONDS == unit) {
075                // okay this is too fast so use a little more delay to avoid CPU burning cycles
076                // To avoid align with java 11 impl of
077                // "java.util.concurrent.locks.AbstractQueuedSynchronizer.SPIN_FOR_TIMEOUT_THRESHOLD", otherwise
078                // no sleep with very high CPU usage
079                answer = 1001L;
080            } else {
081                answer = unit.convert(1, unit);
082            }
083            return answer;
084        }
085
086        @Override
087        @SuppressWarnings("unchecked")
088        public int compareTo(Delayed o) {
089            SubmitOrderFutureTask other = (SubmitOrderFutureTask) o;
090            return (int) (this.id - other.id);
091        }
092
093        @Override
094        protected void done() {
095            // when we are done add to the completion queue
096            completionQueue.add(this);
097        }
098
099        @Override
100        public String toString() {
101            // output using zero-based index
102            return "SubmitOrderedFutureTask[" + (id - 1) + "]";
103        }
104    }
105
106    public SubmitOrderedCompletionService(Executor executor) {
107        this.executor = executor;
108    }
109
110    @Override
111    public Future<V> submit(Callable<V> task) {
112        if (task == null) {
113            throw new IllegalArgumentException("Task must be provided");
114        }
115        SubmitOrderFutureTask f = new SubmitOrderFutureTask(id.incrementAndGet(), task);
116        executor.execute(f);
117        return f;
118    }
119
120    @Override
121    public Future<V> submit(Runnable task, Object result) {
122        if (task == null) {
123            throw new IllegalArgumentException("Task must be provided");
124        }
125        SubmitOrderFutureTask f = new SubmitOrderFutureTask(id.incrementAndGet(), task, null);
126        executor.execute(f);
127        return f;
128    }
129
130    @Override
131    public Future<V> take() throws InterruptedException {
132        index.incrementAndGet();
133        return completionQueue.take();
134    }
135
136    @Override
137    public Future<V> poll() {
138        index.incrementAndGet();
139        Future<V> answer = completionQueue.poll();
140        if (answer == null) {
141            // decrease counter if we didnt get any data
142            index.decrementAndGet();
143        }
144        return answer;
145    }
146
147    @Override
148    public Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException {
149        index.incrementAndGet();
150        Future<V> answer = completionQueue.poll(timeout, unit);
151        if (answer == null) {
152            // decrease counter if we didnt get any data
153            index.decrementAndGet();
154        }
155        return answer;
156    }
157
158    /**
159     * Marks the current task as timeout, which allows you to poll the next tasks which may already have been completed.
160     */
161    public void timeoutTask() {
162        index.incrementAndGet();
163    }
164
165}