/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 WARRANTIESOR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.aries.blueprint.utils.threading;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.aries.blueprint.utils.threading.impl.Discardable;
import org.apache.aries.blueprint.utils.threading.impl.DiscardableCallable;
import org.apache.aries.blueprint.utils.threading.impl.DiscardableRunnable;
import org.apache.aries.blueprint.utils.threading.impl.WrappedFuture;
import org.apache.aries.blueprint.utils.threading.impl.WrappedScheduledFuture;
import org.apache.aries.util.tracker.SingleServiceTracker;
import org.apache.aries.util.tracker.SingleServiceTracker.SingleServiceListener;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
/**
* This class looks like a ScheduledExecutorService to the outside world. Internally it uses either
* a scheduled thread pool with a core size of 3, or it picks one up from the service registry. If
* it picks one up from the service registry then it shuts the internal one down. This doesn't fully meet
* the spec for a SchedueledExecutorService. It does not properly implement shutdownNow, but this isn't used
* by blueprint so for now that should be fine.
*
* <p>It also wraps the Runnables and Callables so when a task is canceled we quickly clean up memory rather
* than waiting for the target to get to the task and purge it.
* </p>
*/
public class ScheduledExecutorServiceWrapper implements ScheduledExecutorService, SingleServiceListener
{
public static interface ScheduledExecutorServiceFactory
{
public ScheduledExecutorService create(String name);
}
private final AtomicReference<ScheduledExecutorService> _current = new AtomicReference<ScheduledExecutorService>();
private SingleServiceTracker<ScheduledExecutorService> _tracked;
private final AtomicReference<ScheduledExecutorService> _default = new AtomicReference<ScheduledExecutorService>();
private final AtomicBoolean _shutdown = new AtomicBoolean();
private final Queue<Discardable<Runnable>> _unprocessedWork = new LinkedBlockingQueue<Discardable<Runnable>>();
private final RWLock _lock = new RWLock();
private final AtomicInteger _invokeEntryCount = new AtomicInteger();
private final ScheduledExecutorServiceFactory _factory;
private final String _name;
public ScheduledExecutorServiceWrapper(BundleContext context, String name, ScheduledExecutorServiceFactory sesf)
{
_name = name;
_factory = sesf;
try {
_tracked = new SingleServiceTracker<ScheduledExecutorService>(context, ScheduledExecutorService.class, "(aries.blueprint.poolName=" + _name + ")", this);
_tracked.open();
} catch (InvalidSyntaxException e) {
// Just ignore and stick with the default one.
}
if (_current.get() == null) {
_default.set(_factory.create(name));
if (!!!_current.compareAndSet(null, _default.get())) {
_default.getAndSet(null).shutdown();
}
}
}
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
{
long timeLeftToWait = unit.toMillis(timeout);
long pausePeriod = timeLeftToWait;
if (pausePeriod > 1000) pausePeriod = 1000;
while (!!!_unprocessedWork.isEmpty() && _invokeEntryCount.get() > 0 && timeLeftToWait > 0) {
Thread.sleep(pausePeriod);
timeLeftToWait -= pausePeriod;
if (timeLeftToWait < pausePeriod) pausePeriod = timeLeftToWait;
}
return _unprocessedWork.isEmpty() && _invokeEntryCount.get() > 0;
}
public <T> List<Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks)
throws InterruptedException
{
try {
return runUnlessShutdown(new Callable<List<Future<T>>>() {
public List<Future<T>> call() throws Exception
{
_invokeEntryCount.incrementAndGet();
try {
return _current.get().invokeAll(tasks);
} finally {
_invokeEntryCount.decrementAndGet();
}
}
});
} catch (InterruptedException e) { throw e;
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public <T> List<Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks,
final long timeout,
final TimeUnit unit) throws InterruptedException
{
try {
return runUnlessShutdown(new Callable<List<Future<T>>>() {
public List<Future<T>> call() throws Exception
{
_invokeEntryCount.incrementAndGet();
try {
return _current.get().invokeAll(tasks, timeout, unit);
} finally {
_invokeEntryCount.decrementAndGet();
}
}
});
} catch (InterruptedException e) { throw e;
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public <T> T invokeAny(final Collection<? extends Callable<T>> tasks) throws InterruptedException,
ExecutionException
{
try {
return runUnlessShutdown(new Callable<T>() {
public T call() throws Exception
{
_invokeEntryCount.incrementAndGet();
try {
return _current.get().invokeAny(tasks);
} finally {
_invokeEntryCount.decrementAndGet();
}
}
});
} catch (InterruptedException e) { throw e;
} catch (ExecutionException e) { throw e;
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public <T> T invokeAny(final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException
{
try {
return runUnlessShutdown(new Callable<T>() {
public T call() throws Exception
{
_invokeEntryCount.incrementAndGet();
try {
return _current.get().invokeAny(tasks, timeout, unit);
} finally {
_invokeEntryCount.decrementAndGet();
}
}
});
} catch (InterruptedException e) { throw e;
} catch (ExecutionException e) { throw e;
} catch (TimeoutException e) { throw e;
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public boolean isShutdown()
{
return _shutdown.get();
}
public boolean isTerminated()
{
if (isShutdown()) return _unprocessedWork.isEmpty();
else return false;
}
public void shutdown()
{
_lock.runWriteOperation(new Runnable() {
public void run()
{
_shutdown.set(true);
ScheduledExecutorService s = _default.get();
if (s != null) s.shutdown();
}
});
}
public List<Runnable> shutdownNow()
{
try {
return _lock.runWriteOperation(new Callable<List<Runnable>>() {
public List<Runnable> call()
{
_shutdown.set(true);
ScheduledExecutorService s = _default.get();
if (s != null) s.shutdownNow();
List<Runnable> runnables = new ArrayList<Runnable>();
for (Discardable<Runnable> r : _unprocessedWork) {
Runnable newRunnable = r.discard();
if (newRunnable != null) {
runnables.add(newRunnable);
}
}
return runnables;
}
});
} catch (Exception e) {
// This wont happen since our callable doesn't throw any exceptions, so we just return an empty list
return Collections.emptyList();
}
}
public <T> Future<T> submit(final Callable<T> task)
{
try {
return runUnlessShutdown(new Callable<Future<T>>() {
public Future<T> call() throws Exception
{
DiscardableCallable<T> t = new DiscardableCallable<T>(task, _unprocessedWork);
try {
return new WrappedFuture<T>(_current.get().submit((Callable<T>)t), t) ;
} catch (RuntimeException e) {
t.discard();
throw e;
}
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public Future<?> submit(final Runnable task)
{
try {
return runUnlessShutdown(new Callable<Future<?>>() {
public Future<?> call()
{
DiscardableRunnable t = new DiscardableRunnable(task, _unprocessedWork);
try {
return new WrappedFuture(_current.get().submit(t), t);
} catch (RuntimeException e) {
t.discard();
throw e;
}
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public <T> Future<T> submit(final Runnable task, final T result)
{
try {
return runUnlessShutdown(new Callable<Future<T>>() {
public Future<T> call()
{
DiscardableRunnable t = new DiscardableRunnable(task, _unprocessedWork);
try {
return new WrappedFuture<T>(_current.get().submit(t, result), t);
} catch (RuntimeException e) {
t.discard();
throw e;
}
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public void execute(final Runnable command)
{
try {
runUnlessShutdown(new Callable<Object>() {
public Object call()
{
DiscardableRunnable t = new DiscardableRunnable(command, _unprocessedWork);
try {
_current.get().execute(t);
} catch (RuntimeException e) {
t.discard();
throw e;
}
return null;
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public ScheduledFuture<?> schedule(final Runnable command, final long delay, final TimeUnit unit)
{
try {
return runUnlessShutdown(new Callable<ScheduledFuture<?>>() {
public ScheduledFuture<?> call()
{
DiscardableRunnable t = new DiscardableRunnable(command, _unprocessedWork);
try {
return new WrappedScheduledFuture(_current.get().schedule(t, delay, unit), t);
} catch (RuntimeException e) {
t.discard();
throw e;
}
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public <V> ScheduledFuture<V> schedule(final Callable<V> callable, final long delay, final TimeUnit unit)
{
try {
return runUnlessShutdown(new Callable<ScheduledFuture<V>>() {
public ScheduledFuture<V> call()
{
DiscardableCallable<V> c = new DiscardableCallable<V>(callable, _unprocessedWork);
try {
return new WrappedScheduledFuture<V>(_current.get().schedule((Callable<V>)c, delay, unit), c);
} catch (RuntimeException e) {
c.discard();
throw e;
}
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public ScheduledFuture<?> scheduleAtFixedRate(final Runnable command, final long initialDelay, final long period,
final TimeUnit unit)
{
try {
return runUnlessShutdown(new Callable<ScheduledFuture<?>>() {
public ScheduledFuture<?> call()
{
DiscardableRunnable t = new DiscardableRunnable(command, _unprocessedWork);
try {
return new WrappedScheduledFuture(_current.get().scheduleAtFixedRate(t, initialDelay, period, unit), t);
} catch (RuntimeException e) {
t.discard();
throw e;
}
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public ScheduledFuture<?> scheduleWithFixedDelay(final Runnable command, final long initialDelay, final long delay,
final TimeUnit unit)
{
try {
return runUnlessShutdown(new Callable<ScheduledFuture<?>>() {
public ScheduledFuture<?> call()
{
DiscardableRunnable t = new DiscardableRunnable(command, _unprocessedWork);
try {
return new WrappedScheduledFuture(_current.get().scheduleWithFixedDelay(t, initialDelay, delay, unit), t);
} catch (RuntimeException e) {
t.discard();
throw e;
}
}
});
} catch (Exception e) { throw new RejectedExecutionException(); }
}
public void serviceFound()
{
ScheduledExecutorService s = _default.get();
if (_current.compareAndSet(s, _tracked.getService())) {
if (s != null) {
if (_default.compareAndSet(s, null)) {
s.shutdown();
}
}
}
}
// TODO when lost or replaced we need to move work to the "new" _current. This is a huge change because the futures are not currently stored.
public void serviceLost()
{
ScheduledExecutorService s = _default.get();
if (s == null) {
s = _factory.create(_name);
if (_default.compareAndSet(null, s)) {
_current.set(s);
}
}
}
public void serviceReplaced()
{
_current.set(_tracked.getService());
}
private <T> T runUnlessShutdown(final Callable<T> call) throws InterruptedException, ExecutionException, TimeoutException
{
try {
return _lock.runReadOperation(new Callable<T>()
{
public T call() throws Exception
{
if (isShutdown()) throw new RejectedExecutionException();
return call.call();
}
});
} catch (InterruptedException e) { throw e;
} catch (ExecutionException e) { throw e;
} catch (TimeoutException e) { throw e;
} catch (RuntimeException e) { throw e;
} catch (Exception e) { throw new RejectedExecutionException(); }
}
}