diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.java b/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.java deleted file mode 100644 index c1c8fac18..000000000 --- a/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.java +++ /dev/null @@ -1,23 +0,0 @@ -package fr.free.nrw.commons.concurrency; - -import androidx.annotation.NonNull; - -import fr.free.nrw.commons.BuildConfig; - -public class BackgroundPoolExceptionHandler implements ExceptionHandler { - /** - * If an exception occurs on a background thread, this handler will crash for debug builds - * but fail silently for release builds. - * @param t - */ - @Override - public void onException(@NonNull final Throwable t) { - //Crash for debug build - if (BuildConfig.DEBUG) { - Thread thread = new Thread(() -> { - throw new RuntimeException(t); - }); - thread.start(); - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.kt b/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.kt new file mode 100644 index 000000000..378a98893 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/BackgroundPoolExceptionHandler.kt @@ -0,0 +1,21 @@ +package fr.free.nrw.commons.concurrency + +import fr.free.nrw.commons.BuildConfig + + +class BackgroundPoolExceptionHandler : ExceptionHandler { + /** + * If an exception occurs on a background thread, this handler will crash for debug builds + * but fail silently for release builds. + * @param t + */ + override fun onException(t: Throwable) { + // Crash for debug build + if (BuildConfig.DEBUG) { + val thread = Thread { + throw RuntimeException(t) + } + thread.start() + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.java b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.java deleted file mode 100644 index 80931b1c1..000000000 --- a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.java +++ /dev/null @@ -1,39 +0,0 @@ -package fr.free.nrw.commons.concurrency; - -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; - -class ExceptionAwareThreadPoolExecutor extends ScheduledThreadPoolExecutor { - - private final ExceptionHandler exceptionHandler; - - public ExceptionAwareThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, - ExceptionHandler exceptionHandler) { - super(corePoolSize, threadFactory); - this.exceptionHandler = exceptionHandler; - } - - @Override - protected void afterExecute(Runnable r, Throwable t) { - super.afterExecute(r, t); - if (t == null && r instanceof Future) { - try { - Future future = (Future) r; - if (future.isDone()) future.get(); - } catch (CancellationException | InterruptedException e) { - //ignore - } catch (ExecutionException e) { - t = e.getCause() != null ? e.getCause() : e; - } catch (Exception e) { - t = e; - } - } - - if (t != null) { - exceptionHandler.onException(t); - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.kt b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.kt new file mode 100644 index 000000000..0efe057f2 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionAwareThreadPoolExecutor.kt @@ -0,0 +1,40 @@ +package fr.free.nrw.commons.concurrency + +import java.util.concurrent.CancellationException +import java.util.concurrent.ExecutionException +import java.util.concurrent.Future +import java.util.concurrent.ScheduledThreadPoolExecutor +import java.util.concurrent.ThreadFactory + + +class ExceptionAwareThreadPoolExecutor( + corePoolSize: Int, + threadFactory: ThreadFactory, + private val exceptionHandler: ExceptionHandler? +) : ScheduledThreadPoolExecutor(corePoolSize, threadFactory) { + + override fun afterExecute(r: Runnable, t: Throwable?) { + super.afterExecute(r, t) + var throwable = t + + if (throwable == null && r is Future<*>) { + try { + if (r.isDone) { + r.get() + } + } catch (e: CancellationException) { + // ignore + } catch (e: InterruptedException) { + // ignore + } catch (e: ExecutionException) { + throwable = e.cause ?: e + } catch (e: Exception) { + throwable = e + } + } + + throwable?.let { + exceptionHandler?.onException(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.java b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.java deleted file mode 100644 index 38690305a..000000000 --- a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package fr.free.nrw.commons.concurrency; - -import androidx.annotation.NonNull; - -public interface ExceptionHandler { - void onException(@NonNull Throwable t); -} diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.kt b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.kt new file mode 100644 index 000000000..6b3d2a0f7 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/ExceptionHandler.kt @@ -0,0 +1,7 @@ +package fr.free.nrw.commons.concurrency + +interface ExceptionHandler { + + fun onException(t: Throwable) + +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolService.java b/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolService.java deleted file mode 100644 index f057f61b2..000000000 --- a/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolService.java +++ /dev/null @@ -1,124 +0,0 @@ -package fr.free.nrw.commons.concurrency; - -import androidx.annotation.NonNull; - -import java.util.concurrent.Callable; -import java.util.concurrent.Executor; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -/** - * This class is a thread pool which provides some additional features: - * - it sets the thread priority to a value lower than foreground priority by default, or you can - * supply your own priority - * - it gives you a way to handle exceptions thrown in the thread pool - */ - -public class ThreadPoolService implements Executor { - private final ScheduledThreadPoolExecutor backgroundPool; - - private ThreadPoolService(final Builder b) { - backgroundPool = new ExceptionAwareThreadPoolExecutor(b.poolSize, - new ThreadFactory() { - int count = 0; - @Override - public Thread newThread(@NonNull Runnable r) { - count++; - Thread t = new Thread(r, String.format("%s-%s", b.name, count)); - //If the priority is specified out of range, we set the thread priority to Thread.MIN_PRIORITY - //It's done prevent IllegalArgumentException and to prevent setting of improper high priority for a less priority task - t.setPriority(b.priority > Thread.MAX_PRIORITY || b.priority < Thread.MIN_PRIORITY ? - Thread.MIN_PRIORITY : b.priority); - return t; - } - }, b.exceptionHandler); - } - - public ScheduledFuture schedule(Callable callable, long time, TimeUnit timeUnit) { - return backgroundPool.schedule(callable, time, timeUnit); - } - - public ScheduledFuture schedule(Runnable runnable) { - return schedule(runnable, 0, TimeUnit.SECONDS); - } - - public ScheduledFuture schedule(Runnable runnable, long time, TimeUnit timeUnit) { - return backgroundPool.schedule(runnable, time, timeUnit); - } - - public ScheduledFuture scheduleAtFixedRate(final Runnable task, long initialDelay, - long period, final TimeUnit timeUnit) { - return backgroundPool.scheduleAtFixedRate(task, initialDelay, period, timeUnit); - } - - public ScheduledThreadPoolExecutor executor() { - return backgroundPool; - } - - public void shutdown(){ - backgroundPool.shutdown(); - } - - @Override - public void execute(Runnable command) { - backgroundPool.execute(command); - } - - /** - * Builder class for {@link ThreadPoolService} - */ - public static class Builder { - //Required - private final String name; - - //Optional - private int poolSize = 1; - private int priority = Thread.MIN_PRIORITY; - private ExceptionHandler exceptionHandler = null; - - /** - * @param name the name of the threads in the service. if there are N threads, - * the thread names will be like name-1, name-2, name-3,...,name-N - */ - public Builder(@NonNull String name) { - this.name = name; - } - - /** - * @param poolSize the number of threads to keep in the pool - * @throws IllegalArgumentException if size of pool <=0 - */ - public Builder setPoolSize(int poolSize) throws IllegalArgumentException { - if (poolSize <= 0) { - throw new IllegalArgumentException("Pool size must be grater than 0"); - } - this.poolSize = poolSize; - return this; - } - - /** - * @param priority Priority of the threads in the service. You can supply a constant from - * {@link java.lang.Thread} or - * specify your own priority in the range 1(MIN_PRIORITY) to 10(MAX_PRIORITY) - * By default, the priority is set to {@link java.lang.Thread#MIN_PRIORITY} - */ - public Builder setPriority(int priority) { - this.priority = priority; - return this; - } - - /** - * @param handler The handler to use to handle exceptions in the service - */ - public Builder setExceptionHandler(ExceptionHandler handler) { - this.exceptionHandler = handler; - return this; - } - - public ThreadPoolService build() { - return new ThreadPoolService(this); - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolService.kt b/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolService.kt new file mode 100644 index 000000000..46138d676 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/concurrency/ThreadPoolService.kt @@ -0,0 +1,122 @@ +package fr.free.nrw.commons.concurrency + +import java.util.concurrent.Callable +import java.util.concurrent.Executor +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.ScheduledThreadPoolExecutor +import java.util.concurrent.ThreadFactory +import java.util.concurrent.TimeUnit + + +/** + * This class is a thread pool which provides some additional features: + * - it sets the thread priority to a value lower than foreground priority by default, or you can + * supply your own priority + * - it gives you a way to handle exceptions thrown in the thread pool + */ +class ThreadPoolService private constructor(builder: Builder) : Executor { + private val backgroundPool: ScheduledThreadPoolExecutor = ExceptionAwareThreadPoolExecutor( + builder.poolSize, + object : ThreadFactory { + private var count = 0 + override fun newThread(r: Runnable): Thread { + count++ + val t = Thread(r, "${builder.name}-$count") + // If the priority is specified out of range, we set the thread priority to + // Thread.MIN_PRIORITY + // It's done to prevent IllegalArgumentException and to prevent setting of + // improper high priority for a less priority task + t.priority = + if ( + builder.priority > Thread.MAX_PRIORITY + || + builder.priority < Thread.MIN_PRIORITY + ) { + Thread.MIN_PRIORITY + } else { + builder.priority + } + return t + } + }, + builder.exceptionHandler + ) + + fun schedule(callable: Callable, time: Long, timeUnit: TimeUnit): ScheduledFuture { + return backgroundPool.schedule(callable, time, timeUnit) + } + + fun schedule(runnable: Runnable): ScheduledFuture<*> { + return schedule(runnable, 0, TimeUnit.SECONDS) + } + + fun schedule(runnable: Runnable, time: Long, timeUnit: TimeUnit): ScheduledFuture<*> { + return backgroundPool.schedule(runnable, time, timeUnit) + } + + fun scheduleAtFixedRate( + task: Runnable, + initialDelay: Long, + period: Long, + timeUnit: TimeUnit + ): ScheduledFuture<*> { + return backgroundPool.scheduleWithFixedDelay(task, initialDelay, period, timeUnit) + } + + fun executor(): ScheduledThreadPoolExecutor { + return backgroundPool + } + + fun shutdown() { + backgroundPool.shutdown() + } + + override fun execute(command: Runnable) { + backgroundPool.execute(command) + } + + /** + * Builder class for [ThreadPoolService] + */ + class Builder(val name: String) { + var poolSize: Int = 1 + var priority: Int = Thread.MIN_PRIORITY + var exceptionHandler: ExceptionHandler? = null + + /** + * @param poolSize the number of threads to keep in the pool + * @throws IllegalArgumentException if size of pool <= 0 + */ + fun setPoolSize(poolSize: Int): Builder { + if (poolSize <= 0) { + throw IllegalArgumentException("Pool size must be greater than 0") + } + this.poolSize = poolSize + return this + } + + /** + * @param priority Priority of the threads in the service. You can supply a constant from + * [java.lang.Thread] or + * specify your own priority in the range 1(MIN_PRIORITY) + * to 10(MAX_PRIORITY) + * By default, the priority is set to [java.lang.Thread.MIN_PRIORITY] + */ + fun setPriority(priority: Int): Builder { + this.priority = priority + return this + } + + /** + * @param handler The handler to use to handle exceptions in the service + */ + fun setExceptionHandler(handler: ExceptionHandler): Builder { + exceptionHandler = handler + return this + } + + fun build(): ThreadPoolService { + return ThreadPoolService(this) + } + } +} \ No newline at end of file