Skip to content

Commit

Permalink
Implement ErrorReceivers support (#45)
Browse files Browse the repository at this point in the history
* Implement TryOnError support

This implements support for trying delegate onError calls if they opt in by implementing a new `TryOnError` interface.

This will likely need to rely on guarded delegate call machinery, and need to figure out whether that should be another config or just supersede/defer to the existing config.

Resolves #24

* Split reportError and createError

* Add DeliverModifiedException marker type

* Update observers for new DeliverModifiedException API

* Rename new marker interfaces

TryOnError -> RxDogTagErrorReceiver
DeliverModifiedException -> RxDogTagModifiedExceptionReceiver

* Rename new marker interfaces

TryOnError -> RxDogTagErrorReceiver
DeliverModifiedException -> RxDogTagModifiedExceptionReceiver

* Spotless

* Spotless
  • Loading branch information
ZacSweers authored Sep 15, 2019
1 parent 0453cd3 commit 864fba8
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 41 deletions.
2 changes: 1 addition & 1 deletion gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def versions = [
jmhPlugin: '0.4.8',
nullawayPlugin: '0.1',
dokka: '0.9.18',
spotless: '3.21.1'
spotless: '3.24.2'
]

def build = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.uber.rxdogtag;

import static com.uber.rxdogtag.RxDogTag.createException;
import static com.uber.rxdogtag.RxDogTag.guardedDelegateCall;
import static com.uber.rxdogtag.RxDogTag.reportError;

Expand Down Expand Up @@ -55,7 +56,17 @@ public void onSubscribe(Disposable d) {

@Override
public void onError(Throwable e) {
reportError(config, t, e, null);
if (delegate instanceof RxDogTagErrorReceiver) {
if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
delegate.onError(createException(config, t, e, null));
} else if (config.guardObserverCallbacks) {
guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
} else {
delegate.onError(e);
}
} else {
reportError(config, t, e, null);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.uber.rxdogtag;

import static com.uber.rxdogtag.RxDogTag.createException;
import static com.uber.rxdogtag.RxDogTag.guardedDelegateCall;
import static com.uber.rxdogtag.RxDogTag.reportError;

Expand Down Expand Up @@ -68,7 +69,17 @@ public void onSuccess(T t) {

@Override
public void onError(Throwable e) {
reportError(config, t, e, null);
if (delegate instanceof RxDogTagErrorReceiver) {
if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
delegate.onError(createException(config, t, e, null));
} else if (config.guardObserverCallbacks) {
guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
} else {
delegate.onError(e);
}
} else {
reportError(config, t, e, null);
}
}

@Override
Expand Down
13 changes: 12 additions & 1 deletion rxdogtag/src/main/java/com/uber/rxdogtag/DogTagObserver.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.uber.rxdogtag;

import static com.uber.rxdogtag.RxDogTag.createException;
import static com.uber.rxdogtag.RxDogTag.guardedDelegateCall;
import static com.uber.rxdogtag.RxDogTag.reportError;

Expand Down Expand Up @@ -66,7 +67,17 @@ public void onNext(T t) {

@Override
public void onError(Throwable e) {
reportError(config, t, e, null);
if (delegate instanceof RxDogTagErrorReceiver) {
if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
delegate.onError(createException(config, t, e, null));
} else if (config.guardObserverCallbacks) {
guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
} else {
delegate.onError(e);
}
} else {
reportError(config, t, e, null);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.uber.rxdogtag;

import static com.uber.rxdogtag.RxDogTag.createException;
import static com.uber.rxdogtag.RxDogTag.guardedDelegateCall;
import static com.uber.rxdogtag.RxDogTag.reportError;

Expand Down Expand Up @@ -67,7 +68,17 @@ public void onSuccess(T t) {

@Override
public void onError(Throwable e) {
reportError(config, t, e, null);
if (delegate instanceof RxDogTagErrorReceiver) {
if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
delegate.onError(createException(config, t, e, null));
} else if (config.guardObserverCallbacks) {
guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
} else {
delegate.onError(e);
}
} else {
reportError(config, t, e, null);
}
}

@Override
Expand Down
13 changes: 12 additions & 1 deletion rxdogtag/src/main/java/com/uber/rxdogtag/DogTagSubscriber.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.uber.rxdogtag;

import static com.uber.rxdogtag.RxDogTag.createException;
import static com.uber.rxdogtag.RxDogTag.guardedDelegateCall;
import static com.uber.rxdogtag.RxDogTag.reportError;

Expand Down Expand Up @@ -72,7 +73,17 @@ public void onNext(T t) {

@Override
public void onError(Throwable e) {
reportError(config, t, e, null);
if (delegate instanceof RxDogTagErrorReceiver) {
if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
delegate.onError(createException(config, t, e, null));
} else if (config.guardObserverCallbacks) {
guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
} else {
delegate.onError(e);
}
} else {
reportError(config, t, e, null);
}
}

@Override
Expand Down
76 changes: 46 additions & 30 deletions rxdogtag/src/main/java/com/uber/rxdogtag/RxDogTag.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,23 @@ public static void install() {
new Builder().install();
}

private static boolean shouldDecorate(Object observerToCheck) {
if (observerToCheck instanceof RxDogTagErrorReceiver) {
return true;
} else if (observerToCheck instanceof LambdaConsumerIntrospection) {
return !((LambdaConsumerIntrospection) observerToCheck).hasCustomOnError();
}
return false;
}

private static synchronized void installWithBuilder(final Configuration config) {
RxJavaPlugins.setOnObservableSubscribe(
(observable, originalObserver) -> {
for (ObserverHandler handler : config.observerHandlers) {
Observer observerToCheck = handler.handle(observable, originalObserver);
if (observerToCheck instanceof LambdaConsumerIntrospection) {
if (!((LambdaConsumerIntrospection) observerToCheck).hasCustomOnError()) {
//noinspection unchecked
return new DogTagObserver(config, originalObserver);
}
if (shouldDecorate(observerToCheck)) {
//noinspection unchecked
return new DogTagObserver(config, originalObserver);
}
}
return originalObserver;
Expand All @@ -121,11 +128,9 @@ private static synchronized void installWithBuilder(final Configuration config)
(flowable, originalSubscriber) -> {
for (ObserverHandler handler : config.observerHandlers) {
Subscriber subscriberToCheck = handler.handle(flowable, originalSubscriber);
if (subscriberToCheck instanceof LambdaConsumerIntrospection) {
if (!((LambdaConsumerIntrospection) subscriberToCheck).hasCustomOnError()) {
//noinspection unchecked
return new DogTagSubscriber(config, originalSubscriber);
}
if (shouldDecorate(subscriberToCheck)) {
//noinspection unchecked
return new DogTagSubscriber(config, originalSubscriber);
}
}
return originalSubscriber;
Expand All @@ -134,11 +139,9 @@ private static synchronized void installWithBuilder(final Configuration config)
(single, originalObserver) -> {
for (ObserverHandler handler : config.observerHandlers) {
SingleObserver observerToCheck = handler.handle(single, originalObserver);
if (observerToCheck instanceof LambdaConsumerIntrospection) {
if (!((LambdaConsumerIntrospection) observerToCheck).hasCustomOnError()) {
//noinspection unchecked
return new DogTagSingleObserver(config, originalObserver);
}
if (shouldDecorate(observerToCheck)) {
//noinspection unchecked
return new DogTagSingleObserver(config, originalObserver);
}
}
return originalObserver;
Expand All @@ -147,11 +150,9 @@ private static synchronized void installWithBuilder(final Configuration config)
(maybe, originalObserver) -> {
for (ObserverHandler handler : config.observerHandlers) {
MaybeObserver observerToCheck = handler.handle(maybe, originalObserver);
if (observerToCheck instanceof LambdaConsumerIntrospection) {
if (!((LambdaConsumerIntrospection) observerToCheck).hasCustomOnError()) {
//noinspection unchecked
return new DogTagMaybeObserver(config, originalObserver);
}
if (shouldDecorate(observerToCheck)) {
//noinspection unchecked
return new DogTagMaybeObserver(config, originalObserver);
}
}
return originalObserver;
Expand All @@ -160,10 +161,8 @@ private static synchronized void installWithBuilder(final Configuration config)
(completable, originalObserver) -> {
for (ObserverHandler handler : config.observerHandlers) {
CompletableObserver observerToCheck = handler.handle(completable, originalObserver);
if (observerToCheck instanceof LambdaConsumerIntrospection) {
if (!((LambdaConsumerIntrospection) observerToCheck).hasCustomOnError()) {
return new DogTagCompletableObserver(config, originalObserver);
}
if (shouldDecorate(observerToCheck)) {
return new DogTagCompletableObserver(config, originalObserver);
}
}
return originalObserver;
Expand Down Expand Up @@ -222,27 +221,28 @@ static void guardedDelegateCall(NonCheckingConsumer<Throwable> errorConsumer, Ru
} catch (OnErrorNotImplementedException e) {
Throwable cause = e.getCause();
errorConsumer.accept(cause);
} catch (Throwable t) {
// This should only happen in cases where we're guarding a call to onError()
errorConsumer.accept(t);
} finally {
Thread.currentThread().setUncaughtExceptionHandler(h);
}
}

/**
* Reports a new {@link OnErrorNotImplementedException} instance with an empty stacktrace and its
* Creates a new {@link OnErrorNotImplementedException} instance with an empty stacktrace and its
* cause with a modified stacktrace. If the original cause is not an instance of
* OnErrorNotImplementedException, a new one is created with the cause as its original cause. The
* new modified stacktrace contains a line pointing to the inferred subscribe point, and then the
* trace of the original {@code originalCause} (as the actual trace for the created trace is
* irrelevant). The message of the exception is the message from the {@code originalCause}, if
* present, and an empty string otherwise.
*
* <p>Reporting is done via {@link RxJavaPlugins#onError(Throwable)}.
*
* @param stackSource the source throwable to extract a stack element tag from.
* @param originalCause the cause of the original error.
* @param callbackType optional callback type of the original exception (onComplete, onNext, etc).
* @return the created exception.
*/
static void reportError(
static OnErrorNotImplementedException createException(
Configuration config,
Throwable stackSource,
Throwable originalCause,
Expand Down Expand Up @@ -312,7 +312,23 @@ static void reportError(
}
}
cause.setStackTrace(newTrace);
RxJavaPlugins.onError(error);
return error;
}

/**
* Shorthand for {@link #createException(Configuration, Throwable, Throwable, String)} + {@link
* RxJavaPlugins#onError(Throwable)}.
*
* @param stackSource the source throwable to extract a stack element tag from.
* @param originalCause the cause of the original error.
* @param callbackType optional callback type of the original exception (onComplete, onNext, etc).
*/
static void reportError(
Configuration config,
Throwable stackSource,
Throwable originalCause,
@Nullable String callbackType) {
RxJavaPlugins.onError(createException(config, stackSource, originalCause, callbackType));
}

private static boolean containsAnyPackages(String input, Set<String> ignorablePackages) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2019. Uber Technologies
*
* Licensed 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 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.uber.rxdogtag;

import io.reactivex.annotations.NonNull;

/**
* A marker type to indicate that RxDogTag's decorating observers should try the onError() of the
* delegate of the observer that implements this.
*/
public interface RxDogTagErrorReceiver {
/**
* Called once if the deferred computation 'throws' an exception.
*
* @param e the exception, not null.
*/
void onError(@NonNull Throwable e);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2019. Uber Technologies
*
* Licensed 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 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.uber.rxdogtag;

import io.reactivex.exceptions.OnErrorNotImplementedException;

/**
* A marker type to indicate that RxDogTag's should pass the decorated stacktrace to {@link
* #onError(Throwable)}. Note that this will always be tried directly, and no guarded delegate
* behavior will be attempted even if {@link RxDogTag.Builder#guardObserverCallbacks(boolean)} is
* enabled in configuration.
*
* <p><em>NOTE:</em> RxDogTag exceptions are always {@link OnErrorNotImplementedException
* OnErrorNotImplementedExceptions}, as these have special behavior as an escape hatch in RxJava
* internals. This exception will have no "cause" property if the original exception was not already
* an {@link OnErrorNotImplementedException}. If it was, which is unusual, it is reused with its
* original cause (if any) and has its stacktrace modified.
*/
public interface RxDogTagTaggedExceptionReceiver extends RxDogTagErrorReceiver {}
Loading

0 comments on commit 864fba8

Please sign in to comment.