Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.x: RxJavaPlugins unwrapRunnable #5734

Merged
merged 24 commits into from
Dec 4, 2017
Merged

2.x: RxJavaPlugins unwrapRunnable #5734

merged 24 commits into from
Dec 4, 2017

Conversation

lukaszguz
Copy link
Contributor

Hi,
RxJava2 version: [2.1.6]
It is reference to #5733

import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.Exceptions;
import io.reactivex.functions.Function;
import io.reactivex.internal.disposables.*;
import io.reactivex.internal.schedulers.*;
import io.reactivex.internal.disposables.EmptyDisposable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure your IDE doesn't unroll star imports to reduce the diff size.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


package io.reactivex.internal.schedulers;

public interface SchedulerRunnableWrapper extends Runnable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document this interface a bit and include a @since 2.1.7 - experimental tag and the @Experimental class annotation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


public interface SchedulerRunnableWrapper extends Runnable {

Runnable getWrappedRunnable();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short description of this method and the @Nullable annotation (some terminated wrappers do null out their wrapped Runnable instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

* Unwraps internal scheduler's task.
* @param task the internal task
* @return the unwrapped runnable or task
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add @since 2.1.7 - experimental and @Experimental annotation to the method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alos please add @Nullable and @CheckReturnValue to the return type, @NonNull to the parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

import io.reactivex.Scheduler.Worker;
import io.reactivex.disposables.*;
import io.reactivex.disposables.Disposable;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't unroll star imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@akarnokd akarnokd added this to the 2.2 milestone Nov 21, 2017
@codecov
Copy link

codecov bot commented Nov 21, 2017

Codecov Report

Merging #5734 into 2.x will increase coverage by 0.07%.
The diff coverage is 100%.

Impacted file tree graph

@@             Coverage Diff              @@
##                2.x    #5734      +/-   ##
============================================
+ Coverage     96.28%   96.35%   +0.07%     
+ Complexity     5837     5833       -4     
============================================
  Files           634      634              
  Lines         41644    41650       +6     
  Branches       5768     5769       +1     
============================================
+ Hits          40096    40131      +35     
+ Misses          609      598      -11     
+ Partials        939      921      -18
Impacted Files Coverage Δ Complexity Δ
src/main/java/io/reactivex/Scheduler.java 100% <100%> (ø) 11 <0> (ø) ⬇️
...activex/internal/schedulers/ExecutorScheduler.java 98.66% <100%> (+2.04%) 9 <0> (ø) ⬇️
...ctivex/internal/schedulers/AbstractDirectTask.java 100% <100%> (ø) 17 <1> (+1) ⬆️
...in/java/io/reactivex/subjects/BehaviorSubject.java 86.97% <0%> (-5.73%) 57% <0%> (ø)
.../io/reactivex/internal/functions/ObjectHelper.java 94.73% <0%> (-5.27%) 20% <0%> (-1%)
...l/operators/observable/ObservableFlatMapMaybe.java 90.84% <0%> (-3.27%) 2% <0%> (ø)
...a/io/reactivex/internal/util/QueueDrainHelper.java 58.86% <0%> (-2.84%) 31% <0%> (-2%)
...java/io/reactivex/subjects/CompletableSubject.java 97.4% <0%> (-2.6%) 36% <0%> (-1%)
...tivex/internal/schedulers/TrampolineScheduler.java 92.3% <0%> (-2.57%) 6% <0%> (ø)
...nternal/operators/parallel/ParallelSortedJoin.java 92.75% <0%> (-2.18%) 2% <0%> (ø)
... and 28 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 30905fc...3c8aefd. Read the comment docs.

@akarnokd
Copy link
Member

Please also make sure the Scheduler and RxJavaPlugins reman 100% covered.

@lukaszguz
Copy link
Contributor Author

Ok, I think that I've finished work with code coverage so if you will have time to review I'm open to continue discussion.

@@ -368,7 +368,7 @@ public long now(@NonNull TimeUnit unit) {

@Override
public void run() {
decoratedRun.run();
getWrappedRunnable().run();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't turn these into method calls.

Copy link
Contributor Author

@lukaszguz lukaszguz Nov 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you give me small tip? Without invoking getWrappedRunnable I don't have a 100% code coverage in Scheduler.java because I'm using the method only in RxJavaPlugins which has 100% code coverage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this type of task is created and returned, then invoke unwrapRunnable on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like that?

@NonNull
    public Disposable scheduleDirect(@NonNull Runnable run, long delay, @NonNull TimeUnit unit) {
        final Worker w = createWorker();
        final Runnable decoratedRun = RxJavaPlugins.onSchedule(run);
        DisposeTask task = new DisposeTask(decoratedRun, w);
        w.schedule(task, delay, unit);
        return RxJavaPlugins.unwrapRunnable(task);
    }

but in that case I have incompatible types Disposable and Runnable

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, do the unwrapping in the unit test. Also for convenience, the unwrapRunnable should perhaps accept Object instead of Runnable.

@@ -1,48 +1,48 @@
/**
* Copyright (c) 2016-present, RxJava Contributors.
*
* <p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please restore the header to the standard.

Copy link
Contributor Author

@lukaszguz lukaszguz Nov 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't have yours code style and I have still the same problem with formating.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I corrected it.


public class SchedulerTest {

@Test
public void defaultPeriodicTask() {
final int[] count = { 0 };
final int[] count = {0};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't change unrelated tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course I will change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I corrected it.

* @since 2.1.7 - experimental
*/
@Experimental
public interface SchedulerRunnableWrapper extends Runnable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain this should extend Runnable as it may make developers think they should invoke run.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chaged it.

import io.reactivex.*;
import io.reactivex.Scheduler.Worker;
import io.reactivex.annotations.NonNull;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please completely restore this file before this PR so it doesn't show up as diff at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope I've finally had the order. :)

@lukaszguz
Copy link
Contributor Author

I think is ok now. I hope so :)

@lukaszguz
Copy link
Contributor Author

Thank you very much for support. :)

@artem-zinnatullin
Copy link
Contributor

Sorry I didn't get in earlier,

This API seems to be inconsistent with other introspection APIs like LambdaConsumerIntrospection where user has a publicly available interface that can be checked manually and consumed.

While this PR adds very generic method to RxJavaPlugins that can accept any Object (!) and return either null or Runnable.

Shouldn't we make interface public and document how to use it in the RxJavaPlugins. setScheduleHandler()?

Additional thought, probably no one would need it outside of RxJavaPlugins. setScheduleHandler() ie after calls like Scheduler.schedule*() since user has both original and wrapped runnable available for use.

@lukaszguz
Copy link
Contributor Author

Hi,
I've described much more what I want to achieve in #5733
The main problem is that I don't have a wrapped runnable in RxJavaPlugins.scheduleHandler so when I checked if runnable of my type is in scheduleHandler I couldn't get that information because this runnable is wrapped by for example Scheduler.DisposeTask. When I was implementing I followed directions given by @akarnokd .

Do you suggest that adding better description how to use SchedulerRunnableWrapper with RxJavaPlugins will be enough?

Of course I'm open to discussion :)

@lukaszguz
Copy link
Contributor Author

@akarnokd @artem-zinnatullin I made changes based on your tips.

@@ -0,0 +1,35 @@
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this into io.reactivex.schedulers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -43,11 +32,22 @@
import io.reactivex.internal.operators.observable.ObservableRange;
import io.reactivex.internal.operators.parallel.ParallelFromPublisher;
import io.reactivex.internal.operators.single.SingleJust;
import io.reactivex.internal.schedulers.ImmediateThinScheduler;
import io.reactivex.internal.schedulers.*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this doesn't have an extra method, please restore the pre-PR version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -48,6 +37,17 @@
import io.reactivex.observables.ConnectableObservable;
import io.reactivex.parallel.ParallelFlowable;
import io.reactivex.schedulers.Schedulers;
import org.junit.*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still shows up as changed because the imports. Please use the version from HEAD to restore this file.

Copy link
Contributor Author

@lukaszguz lukaszguz Nov 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ehh it is the same mistake :)
I've fixed it.

import io.reactivex.plugins.RxJavaPlugins;

/**
* Marker interface to indicate wrapped action inside internal scheduler's task.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a marker interface if it declares method(s) that can be consumed :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True :)

* Inside of the {@link RxJavaPlugins#onSchedule(Runnable)}, you can unwrap runnable from internal wrappers like
* {@link Scheduler.DisposeTask}.
*
* @since 2.1.7 - experimental
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add "see also" with references to RxJavaPlugins.setScheduleHandler() and RxJavaPlugins. getScheduleHandler()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about something like this?

/**
 * Interface to wrap an action inside internal scheduler's task.
 *
 * You can check if runnable implements this interface and unwrap original runnable.
 * For example inside of the {@link RxJavaPlugins#setScheduleHandler(Function)}
 *
 * @since 2.1.7 - experimental
 */

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I corrected it.

/**
* Marker interface to indicate wrapped action inside internal scheduler's task.
*
* Inside of the {@link RxJavaPlugins#onSchedule(Runnable)}, you can unwrap runnable from internal wrappers like
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change to something like:

You can check if runnable implements this interface and unwrap original runnable?

};
SchedulerRunnableIntrospection wrapper = (SchedulerRunnableIntrospection) scheduler.schedulePeriodicallyDirect(runnable, 100, 100, TimeUnit.MILLISECONDS);

assertEquals(runnable, wrapper.getWrappedRunnable());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertSame?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed it.

Copy link
Contributor

@artem-zinnatullin artem-zinnatullin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pls fix comments, otherwise LGTM :)

* Inside of the {@link RxJavaPlugins#onSchedule(Runnable)}, you can unwrap runnable from internal wrappers like
* {@link Scheduler.DisposeTask}.
*
* @since 2.1.7 - experimental
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

*
* @return the wrapped action, may be null.
*/
@Nullable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually is @NotNull now since it's not a generic Object based API, schedulers don't expect that Runnable can be null (I'm adding PR that'll enforce that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I corrected it

@@ -307,4 +307,66 @@ public void customScheduleDirectDisposed() {

assertTrue(d.isDisposed());
}

@Test
public void unwrapDefaultPeriodicTask() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we move these tests to AbstractSchedulerTests @akarnokd?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm waiting for your decision :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're waiting for @akarnokd to answer I'd recommend you to try to move this code to AbstractSchedulerTests and make sure to use getScheduler() instead of TestScheduler, that way we check that all schedulers properly support this introspection

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we want to check that all schedulers support this API?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can get complicated and this is an optional introspection. If @lukaszguz wants to expand it, then sure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but I'd encourage @lukaszguz to try that and ask for help if needed, would be great to provide as robust API experience as possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's enough for now? I can always add a new PR with additional tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with this as is. We can always expand the feature set later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my first time when I'm doing PR to your library. Thanks to your tips, I know more or less what your requirements are and I can write better code. I think the better option would be to leave those tests in SchedulerTest because they are showed how Scheduler works but also good idea is to add tests in AbstractSchedulerTests which they will guarantee compatibility with all rx's schedulers

@artem-zinnatullin
Copy link
Contributor

artem-zinnatullin commented Dec 1, 2017 via email

@lukaszguz
Copy link
Contributor Author

lukaszguz commented Dec 1, 2017

I wrote tests to AbstractSchedulerTests :) and I found some mistakes for example in ExecutorScheduler so I added SchedulerRunnableIntrospection to AbstractDirectTask and ExecutorScheduler.DelayedRunnable. Now I think it has already work for all rx's schedulers.
I almost forgot I have left unwrapped test in SchedulerTest I think they show a lot.


@Override
public Runnable getWrappedRunnable() {
return get();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dispose() will swap in a null but the method is defined as @NotNull. So either the interface should allow nulls or this should return Functions.EMPTY_RUNNABLE:

Runnable r = get();
return r != null ? r : Functions.EMPTY_RUNNABLE;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ehhh I did not notice it. I will check what will happen when dispose return Functions.EMPTY_RUNNABLE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All tests were passed with this change. I will leave with:

Runnable r = get();
return r != null ? r : Functions.EMPTY_RUNNABLE;

@Override
public Runnable getWrappedRunnable() {
Runnable r = get();
return r != null ? r : Functions.EMPTY_RUNNABLE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative solution could be to throw IllegalStateException I guess

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a failure state but a terminal state of the wrapper. Penalizing it would be too harsh.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this particular introspection api it seems like an invalid state, but I'm ~fine with current solution.

The problem of returning empty runnable is that since it's the same object every time — it can mess up introspection that is based on object identity/equality

@artem-zinnatullin
Copy link
Contributor

Thanks for addressing feedback!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants