@@ -321,6 +321,8 @@ public static Scheduler single() {
321321 * non-delayed tasks as it can, which may result in a longer than expected occupation of a
322322 * thread of the given backing Executor. In other terms, it does not allow per-Runnable fairness
323323 * in case the worker runs on a shared underlying thread of the Executor.
324+ * See {@link #from(Executor, boolean, boolean)} to create a wrapper that uses the underlying Executor
325+ * more fairly.
324326 * <p>
325327 * Starting, stopping and restarting this scheduler is not supported (no-op) and the provided
326328 * executor's lifecycle must be managed externally:
@@ -346,10 +348,11 @@ public static Scheduler single() {
346348 * @param executor
347349 * the executor to wrap
348350 * @return the new Scheduler wrapping the Executor
351+ * @see #from(Executor, boolean, boolean)
349352 */
350353 @ NonNull
351354 public static Scheduler from (@ NonNull Executor executor ) {
352- return new ExecutorScheduler (executor , false );
355+ return new ExecutorScheduler (executor , false , false );
353356 }
354357
355358 /**
@@ -382,6 +385,8 @@ public static Scheduler from(@NonNull Executor executor) {
382385 * non-delayed tasks as it can, which may result in a longer than expected occupation of a
383386 * thread of the given backing Executor. In other terms, it does not allow per-Runnable fairness
384387 * in case the worker runs on a shared underlying thread of the Executor.
388+ * See {@link #from(Executor, boolean, boolean)} to create a wrapper that uses the underlying Executor
389+ * more fairly.
385390 * <p>
386391 * Starting, stopping and restarting this scheduler is not supported (no-op) and the provided
387392 * executor's lifecycle must be managed externally:
@@ -411,10 +416,82 @@ public static Scheduler from(@NonNull Executor executor) {
411416 * be interrupted when the task is disposed.
412417 * @return the new Scheduler wrapping the Executor
413418 * @since 3.0.0
419+ * @see #from(Executor, boolean, boolean)
414420 */
415421 @ NonNull
416422 public static Scheduler from (@ NonNull Executor executor , boolean interruptibleWorker ) {
417- return new ExecutorScheduler (executor , interruptibleWorker );
423+ return new ExecutorScheduler (executor , interruptibleWorker , false );
424+ }
425+
426+ /**
427+ * Wraps an {@link Executor} into a new Scheduler instance and delegates {@code schedule()}
428+ * calls to it.
429+ * <p>
430+ * The tasks scheduled by the returned {@link Scheduler} and its {@link io.reactivex.rxjava3.core.Scheduler.Worker Scheduler.Worker}
431+ * can be optionally interrupted.
432+ * <p>
433+ * If the provided executor doesn't support any of the more specific standard Java executor
434+ * APIs, tasks scheduled with a time delay or periodically will use the
435+ * {@link #single()} scheduler for the timed waiting
436+ * before posting the actual task to the given executor.
437+ * <p>
438+ * If the provided executor supports the standard Java {@link ExecutorService} API,
439+ * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling
440+ * {@link io.reactivex.rxjava3.disposables.Disposable#dispose()}. In addition, tasks scheduled with
441+ * a time delay or periodically will use the {@link #single()} scheduler for the timed waiting
442+ * before posting the actual task to the given executor.
443+ * <p>
444+ * If the provided executor supports the standard Java {@link ScheduledExecutorService} API,
445+ * canceling tasks scheduled by this scheduler can be cancelled/interrupted by calling
446+ * {@link io.reactivex.rxjava3.disposables.Disposable#dispose()}. In addition, tasks scheduled with
447+ * a time delay or periodically will use the provided executor. Note, however, if the provided
448+ * {@code ScheduledExecutorService} instance is not single threaded, tasks scheduled
449+ * with a time delay close to each other may end up executing in different order than
450+ * the original schedule() call was issued. This limitation may be lifted in a future patch.
451+ * <p>
452+ * The implementation of the Worker of this wrapper Scheduler can operate in both eager (non-fair) and
453+ * fair modes depending on the specified parameter. In <em>eager</em> mode, it will execute as many
454+ * non-delayed tasks as it can, which may result in a longer than expected occupation of a
455+ * thread of the given backing Executor. In other terms, it does not allow per-Runnable fairness
456+ * in case the worker runs on a shared underlying thread of the Executor. In <em>fair</em> mode,
457+ * non-delayed tasks will still be executed in a FIFO and non-overlapping manner, but after each task,
458+ * the execution for the next task is rescheduled with the same underlying Executor, allowing interleaving
459+ * from both the same Scheduler or other external usages of the underlying Executor.
460+ * <p>
461+ * Starting, stopping and restarting this scheduler is not supported (no-op) and the provided
462+ * executor's lifecycle must be managed externally:
463+ * <pre><code>
464+ * ExecutorService exec = Executors.newSingleThreadedExecutor();
465+ * try {
466+ * Scheduler scheduler = Schedulers.from(exec, true, true);
467+ * Flowable.just(1)
468+ * .subscribeOn(scheduler)
469+ * .map(v -> v + 1)
470+ * .observeOn(scheduler)
471+ * .blockingSubscribe(System.out::println);
472+ * } finally {
473+ * exec.shutdown();
474+ * }
475+ * </code></pre>
476+ * <p>
477+ * This type of scheduler is less sensitive to leaking {@link io.reactivex.rxjava3.core.Scheduler.Worker Scheduler.Worker} instances, although
478+ * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or
479+ * execute those tasks "unexpectedly".
480+ * <p>
481+ * Note that this method returns a new {@link Scheduler} instance, even for the same {@link Executor} instance.
482+ * @param executor
483+ * the executor to wrap
484+ * @param interruptibleWorker if {@code true} the tasks submitted to the {@link io.reactivex.rxjava3.core.Scheduler.Worker Scheduler.Worker} will
485+ * be interrupted when the task is disposed.
486+ * @param fair if {@code true} tasks submitted to the will be executed by the underlying {@link Executor} one after the other, still
487+ * in a FIFO and non-overlapping manner, but allows interleaving with other tasks submitted to the underlying {@code Executor}.
488+ * If {@code false}, the underlying FIFO scheme will execute as many tasks as it can before giving up the underlying {@code Executor} thread.
489+ * @return the new Scheduler wrapping the Executor
490+ * @since 3.0.0
491+ */
492+ @ NonNull
493+ public static Scheduler from (@ NonNull Executor executor , boolean interruptibleWorker , boolean fair ) {
494+ return new ExecutorScheduler (executor , interruptibleWorker , fair );
418495 }
419496
420497 /**
0 commit comments