/* * 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(); } } }