I am using the GraphQL.NET client to subscribe to data on a remote service. The client returns an Observable so when the subscription is created you, as expected, receive new messages in onNext and get errors (both initial connection errors, reconnection errors, and anything else) in onError. The GraphQL client has the ability to automatically reconnect if the initial connection fails or when an established connection drops.
I know that by convention, any messages coming in on onError is supposed to terminate the sequence of messages. However, somehow they are able to continue sending to onNext and onError after that first onError. I have tried reading through the code but it is confusing. There seems to be multiple nesting of Observable and I suspect they are creating a new sequence when they encounter an error.
To clarify my issue, suppose I had the following pseudo Event based wrapper class.
public class PubSubSubscription() {
...
public void CreateSubscription<TResponse>(string topic) {
// GraphQL client
var stream = client
.CreateSubscriptionStream<FixConnectionChangedSubscriptionResult>(...);
stream
.Subscribe(
response => {
// Do stuff with incoming data (validation, mapping, logging, etc.)
// send it on the UI
DataReceived?.Invoke(this, new DataReceivedEventArgs { Message = response });
},
ex => {
// ******************************
// Note that the Observable created by CreateSubscriptionStream()
// will call `onError` over-and-over since it _seems_ like it is
// creating (and re-creating) nested Observables in its own
// classes. In the event of an initial connection failure or
// re-connect it will raise an error and then automatically
// try again.
// ******************************
// send it on to UI
ErrorDetected?.Invoke(this, new ErrorDetectedEventArgs { Exception = ex });
});
}
...
}
I would then call it as follows (or close enough)...
...
var orders = ordersPubSub.CreateSubscription("/orders");
orders.DataReceived += OnDataReceived;
orders.ErrorDetected += OnErrorDetected;
void OnErrorDetected(object sender, ErrorDetectedEventArgs e) {
// Can be called multiple times
// Display message in UI
}
...
I am having trouble converting that event-based wrapper approach to an Observable wrapper approach.
public class PubSubSubscription() {
...
public IObservable<TResponse> CreateSubscription<TResponse>(string topic) {
// Observable that I give back to my UI
var eventSubject = new Subject<TResponse>();
// GraphQL client
var stream = client
.CreateSubscriptionStream<FixConnectionChangedSubscriptionResult>(...);
stream
.Subscribe(
response => {
// Do stuff with incoming data (validation, mapping, logging, etc.)
// send it on the UI
eventSubject.onNext(response);
},
ex => {
// ******************************
// Note that the Observable created by CreateSubscriptionStream()
// will call `onError` over-and-over since it _seems_ like it is
// creating (and re-creating) nested Observables in its own
// classes. In the event of an initial connection failure or
// re-connect it will raise an error and then automatically
// try again.
// ******************************
// send it on to UI
eventSubject.onError(ex);
});
return eventSubject.AsObservable();
}
...
}
This I would then call it as follows (or close enough)...
...
var orders = ordersPubSub.CreateSubscription("/orders");
orders
// Things I have tried...
// Do() by itself does not stop the exception from hitting onError (which makes sense)
.Do(
_ => { },
ex => // display in UI)
// Retry() seems to cause the GraphQL subscription to "go away" because I no longer see connection attempts
.Retry()
// Stops the exception from hitting onError but the sequence still stops since I need to return _something_ from this method
.Catch(() => {
// display in UI
return Observable.Empty<T>();
})
.Subscribe(
msg => // do something with data,
ex => // display in UI);
}
...
Bottom line is what is the proper approach to dealing with sequences that can be "temporarily interrupted"?
I am also unsure of the idea of pushing the responsibility of retries onto the observer. This means that I would need to duplicate the logic each time CreateSubscription() is called. Yet, if I move it into the CreateSubscription() method, I am still unsure how to let the observer know the interruption happened so the UI can be updated.
One approach I am playing with (after reading about it as a possible solution) is to wrap my TResponse in a "fake" SubscriptionResponse<TResponse> which has T Value and Exception Error properties so the outer Observable only has onNext called. Then in my Subscribe I add if/else logic to check if Error is non-null and react accordingly. But this just feels ugly... I would almost want to go back to using events...
If you have an unruly observable - one that produces multiple errors without ended - you can make it workable by doing this:
IObservable<int> unruly = ...;
IObservable<Notification<int>> workable =
unruly
.Materialize();
The Materialize operator turns the IObservable<int> into an IObservable<Notification<int>> where the OnCompleted, OnError, and OnNext messages all get converted to OnNext messages that you can inspect like this:
Now you can deal with the errors without the sequence ending. When you've cleared them you can restore the sequence with Dematerialize like so:
IObservable<int> ruly =
workable
.Where(x => x.Kind != NotificationKind.OnError)
.Dematerialize();
Related
I need to call one particular endpoint 18000 times using .NET Core 6. I need to log the response from each endpoint call.
The endpoint takes approx. 2 seconds to respond so I want to get all the requests sent quicker than that :)
Pretty sure Task.WhenAll is my friend here?
You can use an ActionBlock from the Dataflow library for this kind of activity. It will give you control over the level of parallelism and manage all the tasks for you.
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/dataflow-task-parallel-library
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.dataflow.actionblock-1?view=net-7.0
The example on how to use this on msdn is perhaps not as clear as it could be, hopefully the below will be helpful. First define a class to hold any data you need to send to the endpoint and use this as TInput on your ActionBlock, something like this:
class EndpointArguments
{
// what information do you need to submit the request?
}
use it like this:
var endpointActionBlock = new ActionBlock<EndpointArguments>(data =>
{
// This will be invoked for every item you post to the data block.
// Invoke the endpoint using data and log the result
// you can use a policy handler like Polly for catching and retrying failed requests, etc.
},
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 10, // how many concurrent requests do you want?
EnsureOrdered = false // does the order matter?
// check the other options offered by this class
}
);
// Post values to the block.
while (moreValuesToProcess)
{
endpointActionBlock.Post(new EndpointArguments { ... });
}
// Signal to the block we are done adding requests. They will continue running in the background.
endpointActionBlock.Complete();
// Wait for completion in a try/catch block.
try
{
await endpointActionBlock.Completion;
}
catch (AggregateException ae)
{
// If an unhandled exception occurs during dataflow processing, all
// exceptions are propagated through an AggregateException object.
// You probably want to handle the exception in the callback / delegate to prevent this from happening
ae.Handle(e =>
{
Console.WriteLine("Encountered {0}: {1}", e.GetType().Name, e.Message);
return true;
});
}
for (int i = 0; i < 10; i++)
{
new Thread(async () =>
{
//call api here
}).Start();
}
You can try this. Make sure you test it out with a small sample before you call 18000 times.
Recently I become aware that the Rx Finally operator behaves in a way which, at least for me, is unexpected. My expectation was that any error thrown by the finallyAction would be propagated to the operator's observers downstream. Alas this is not what happens. In the reality the operator first propagates the completion (or the failure) of the antecedent sequence to its observers, and then invokes the action, at a point in time when it's not possible to propagate a potential error thrown by the action. So it throws the error on the ThreadPool, and crashes the process. Which is not only unexpected, but also highly problematic. Below is a minimal demonstration of this behavior:
Observable
.Timer(TimeSpan.FromMilliseconds(100))
.Finally(() => throw new ApplicationException("Oops!"))
.Subscribe(_ => { }, ex => Console.WriteLine(ex.Message),
() => Console.WriteLine("Completed"));
Thread.Sleep(1000);
Outcome: Unhandled exception (Fiddle)
The exception thrown by the Finally lambda is not handled by the Subscribe:onError handler, as it would be desirable.
This feature (I am tempted to call it a flaw) limits severely the usefulness of the Finally operator in my eyes. Essentially I can only use it when I want to invoke an action that is expected to never fail, and if it fails it would indicate a catastrophic corruption of the application's state, when no recovery is possible. I could use it for example to Release a SemaphoreSlim (like I've done here for example), which can only fail if my code has a bug. I am OK with my app crashing in this case. But I've also used it recently to invoke an unknown action supplied by the caller, an action that could potentially fail, and crashing the app in this case is unacceptable. Instead, the error should be propagated downstream. So what I am asking here is how to implement a Finally variant (let's call it FinallySafe) with identical signature, and the behavior specified below:
public static IObservable<TSource> FinallySafe<TSource>(
this IObservable<TSource> source, Action finallyAction);
The finallyAction should be invoked after the source sequence has emitted an OnCompleted or an OnError notification, but before this notification is propagated to the observer.
If the finallyAction invocation completed successfully, the original OnCompleted/OnError notification should be propagated to the observer.
If the finallyAction invocation failed, an OnError notification should be propagated to the observer, containing the error that just occurred. In this case the previous error, the one that may have caused the source to complete with failure, should be ignored (not propagated).
The finallyAction should also be invoked when the FinallySafe is unsubscribed before the completion of the source. When a subscriber (observer) disposes a subscription, the finallyAction should by invoked synchronously, and any error should be propagated to the caller of the Dispose method.
If the FinallySafe is subscribed by multiple observers, the finallyAction should be invoked once per subscription, independently for each subscriber, following the rules above. Concurrent invocations are OK.
The finallyAction should never be invoked more than once per subscriber.
Validation: replacing the Finally with the FinallySafe in the code snippet above, should result to a program that doesn't crash with an unhandled exception.
Alternative: I am also willing to accept an answer that provides a reasonable explanation about why the behavior of the built-in Finally operator is better than the behavior of the custom FinallySafe operator, as specified above.
Finally gets called after the sequence has ended, and since the Rx contract only allows one OnError or OnCompleted it can't issue a second one.
But, if you replace the Finally with Do you can get the behaviour that you want.
Try this code:
Observable
.Timer(TimeSpan.FromMilliseconds(100))
.Do(_ => { }, () => throw new ApplicationException("Oops!"))
.Subscribe
(_ => { },
ex => Console.WriteLine(ex.Message),
() => Console.WriteLine("Completed"));
Thread.Sleep(TimeSpan.FromMilliseconds(1000));
That operates as you expect it to.
I get this output:
Oops!
If you want to run something at unsubscribe, then use this extension method:
public static class Ext
{
public static IObservable<T> Unsubscribed<T>(this IObservable<T> source, Action unsubscribed) =>
Observable.Create<T>(o =>
new CompositeDisposable(source.Subscribe(o), Disposable.Create(unsubscribed)));
}
Here's an example of its use:
var source = Observable.Never<int>();
var subscription =
source
.Unsubscribed(() => Console.WriteLine("Unsubscribed"))
.Subscribe();
subscription.Dispose();
That outputs:
Unsubscribed
Here is an implementation of the FinallySafe operator, having the behavior specified in the question:
/// <summary>
/// Invokes a specified action after the source observable sequence terminates
/// successfully or exceptionally. The action is invoked before the propagation
/// of the source's completion, and any exception thrown by the action is
/// propagated to the observer. The action is also invoked if the observer
/// is unsubscribed before the termination of the source sequence.
/// </summary>
public static IObservable<T> FinallySafe<T>(this IObservable<T> source,
Action finallyAction)
{
return Observable.Create<T>(observer =>
{
var finallyOnce = Disposable.Create(finallyAction);
var subscription = source.Subscribe(observer.OnNext, error =>
{
try { finallyOnce.Dispose(); }
catch (Exception ex) { observer.OnError(ex); return; }
observer.OnError(error);
}, () =>
{
try { finallyOnce.Dispose(); }
catch (Exception ex) { observer.OnError(ex); return; }
observer.OnCompleted();
});
return new CompositeDisposable(subscription, finallyOnce);
});
}
The finallyAction is assigned as the Dispose action of a Disposable.Create disposable instance, in order to ensure that the action will be invoked at most once. This disposable is then combined with the disposable subscription of the source, by using a CompositeDisposable instance.
As a side note, I would like to address the question if we could go even further, and propagate downstream a possible error of the finallyAction during the unsubscription. This could be desirable in some cases, but unfortunately it's not possible. First and foremost doing so would violate a guideline, found in The Observable Contract document, that states:
When an observer issues an Unsubscribe notification to an Observable, the Observable will attempt to stop issuing notifications to the observer. It is not guaranteed, however, that the Observable will issue no notifications to the observer after an observer issues it an Unsubscribe notification.
So such an implementation would be non-conforming. Even worse, the Observable.Create method enforces this guideline, by muting the observer immediately after the subscription is disposed. It does so by encapsulating the observer inside an AutoDetachObserver wrapper. And even if we tried to circumvent this limitation by implementing an IObservable<T> type from scratch, any built-in operator that could be attached after our non-conforming Finally operator would mute our post-unsubscription OnError notification anyway. So it's just not possible. An error during the unsubscription cannot be propagated to the subscriber that just requested to unsubscribe.
I read the documentation and now I'm sure. The finally-operator will be called after the completition and should not throw any exception.
Compared to non-reactive programming:
StreamReader file = new StreamReader("file.txt");
string ln;
try {
while ((ln = file.ReadLine()) != null) {
Console.WriteLine(ln);
}
}
finally {
// avoid to throw an exception inside of finally!
if (file != null) {
file.close();
}
}
It is important to not throw an exception inside of finally.
Here is an example howto use it correctly (fiddle):
using System;
using System.Reactive.Linq;
using System.Threading;
public class Program
{
public static void Main()
{
Observable
.Range(1,5) // simulates stream-reader
.Finally(() => Console.WriteLine("Close streamreader"))
.Do(i => {
if (i == 5) {
throw new ApplicationException("Oops!"); // simulates IO-error
}
Console.WriteLine("Read " + i);
})
.Subscribe(_ => { }, ex => Console.WriteLine(ex.Message),
() => Console.WriteLine("Completed"));
Thread.Sleep(1000);
}
}
I'm not sure what you are trying to do (and I'm pretty new to c# reactive), but I think you are using not the right operator.
Edit
But you can patch it, if you want. In this article, they do something familar.
http://introtorx.com/Content/v1.0.10621.0/11_AdvancedErrorHandling.html
I've got a WPF app using ReactiveUI, and it works by periodically fetching state from an external process.
Given a fetch observable as follows:
var latestState =
Observable.Interval(TimeSpan.FromSeconds(.5))
.SelectMany(async _ =>
{
try
{
var state = await _robotsClient.GetRobotsStateAsync(new GetRobotsStateRequest());
return state;
}
catch (Exception)
{
return null;
}
})
.Publish();
I need to be able interrupt the fetching of data, if it fails.
What I want to be able to do, is something like:
var latestState =
Observable.Interval(TimeSpan.FromSeconds(.5))
.SelectMany(async _ =>
{
try
{
var state = await _robotsClient.GetRobotsStateAsync(new GetRobotsStateRequest());
return state;
}
catch (Exception)
{
// Show and await the dialog dismissal
// instructions for starting the external process provided etc etc
await dialogs.ShowErrorMessageAsync("Failed to fetch info", "Failed to get the latest state");
/* MISSING:
* Some magical gubbins that will produce the state on a steady interval, but also still support
* displaying the dialog and halting
*/
return null;
}
})
.Publish();
Obviously that's not feasible, because you end up with a chicken and egg problem.
Every way I've tried to slice this (e.g. using a Subject<bool> to track success / failure) has ultimately resulted in the fact that the failure case still needs to be able to emit an observable that fetches on the interval, and respects the failure handling - but that's not possible from inside the handler.
I'm almost certain this is an issue with conceptualising the way to signal the error / retrieve the data / resume the interval.
Partial solution / implementation based on comment feedback:
var stateTimer = Observable.Interval(TimeSpan.FromSeconds(10));
var stateFetcher =
Observable.FromAsync(async () =>
await _robotsClient.GetRobotsStateAsync(new GetRobotsStateRequest()));
IObservable<GetRobotsStateReply> DisplayStateError(Exception causingException)
=> Observable.FromAsync(async () =>
{
await dialogs.ShowErrorMessageAsync(
"Failed to get robot info",
"Something went wrong");
return new GetRobotsStateReply { };
});
var stateStream =
stateTimer
.SelectMany(stateFetcher)
.Catch((Exception ex) => DisplayStateError(ex))
.Publish();
stateStream.Connect();
This implementation gets me the behaviour I need, and has the benefit of not triggering the timer when displaying the error dialog; however, it doesn't then subsequently trigger after dismissing the dialog (I believe because the stream has been terminated) - I'm going to use suggestion in the comments to fix this and then add an answer.
Working solution (can be added as an answer if reopened).
var fetchTimer = Observable.Timer(TimeSpan.FromSeconds(5));
var stateFetcher = Observable.FromAsync(async () =>
await _robotsClient.GetRobotsStateAsync(new GetRobotsStateRequest()));
var timerFetch = Observable.SelectMany(fetchTimer, stateFetcher);
IObservable<GetRobotsStateReply> GetErrorHandler(Exception ex) =>
Observable.FromAsync(async () =>
{
await dialogs.ShowErrorMessageAsync(
"TEST",
"TEST");
return (GetRobotsStateReply)null;
});
IObservable<GetRobotsStateReply> GetStateFetchCycleObservable(
IObservable<GetRobotsStateReply> source) =>
source
.Catch((Exception ex) => GetErrorHandler(ex))
.SelectMany(state =>
state != null
? GetStateFetchCycleObservable(timerFetch)
: GetStateFetchCycleObservable(stateFetcher));
var latestState =
GetStateFetchCycleObservable(timerFetch)
.Publish();
Thanks to Theodor's suggestions, I've been able to hit on a solution.
I'd made the mistake of not thinking in terms of hot/cold observables and not making proper use of the built-in error handling mechanisms.
I was initially using Observable.Interval but this had the undesired consequence of firing and initiating a new remote request while the previous one was still in-flight (I suppose I could have throttled).
This solution works by using Observable.Timer to set up an initial delay, then make the remote request; this stream is then observed, on error it displays the dialog, and then binds back to the delay + fetch stream.
As the delay + fetch stream is cold, the delay works again as intended, and everything flows back around in a nice loop.
This has been further worked on, as there were issues with double firings of the timer (when using Retry), or the second time around not doing anything after the dialog dismissal.
I realised that was down to the inner observable not having the outer observable's projection back to a value-producing observable.
The new solution manages this, and even solves the problem of immediately re-fetching state if the user dismisses the dialog, or padding with a time interval in the case of a successful result.
Here is my suggestion:
var observable = Observable
.Timer(TimeSpan.Zero, TimeSpan.FromMilliseconds(500))
.Select(x => Observable.FromAsync(async () =>
{
return await _robotsClient.GetRobotsStateAsync(new GetRobotsStateRequest());
}))
.Concat()
.Catch((Exception ex) => Observable.FromAsync<GetRobotsStateReply>(async () =>
{
await dialogs.ShowErrorMessageAsync("Failed to fetch info",
"Failed to get the latest state");
throw ex;
}))
.Retry();
The Timer+Select+Concat operators ensure that the GetRobotsStateAsync will be executed without overlapping. In case of an exception the timer will be discarded, the Catch operator will kick in, and the original error will be rethrown after closing the dialog, in order to trigger the Retry operator. Then everything will be repeated again, with a brand new timer. The loop will keep spinning until the subscription to the observable is disposed.
This solution makes the assumption that the execution of the GetRobotsStateAsync will not exceed the timer's 500 msec interval in a regular basis. Otherwise the ticks produced by the timer will start stacking up (inside the Concat's internal queue), putting the system under memory pressure. For a more sophisticated (but also more complex) periodic mechanism that avoids this problem look at this answer.
i have this code(sample source code from sample code)
i change a little this code for get exception with onError like :
IObservable<int> source = Observable.Range(1, 10);
IDisposable subscription = source.Subscribe<int>(
onNext =>
{
int zero = 0; //added this code
var div = onNext / zero; //and this code for get execption
},
onError =>
{
Console.WriteLine("OnError: {0}", onError.Message); // This code never runs
});
subscription.Dispose();
how to set exception to onError?
The onError here is supposed to catch exception errors emitted by source, not by your callback onNext.
I would say the try ... catch inside onNext is the usual way to go here, though it's understandable that you want to reuse your error handling in onError.
If you want to force this pattern here, what you could do is to pipe your observable to another that executes the code you want to catch, with a Do for instance, and subscribe to the result observable.
something like :
IObservable<int> source = Observable.Range(1, 10);
IDisposable subscription = source.Do(_ =>
{
// Here goes the code you had in your 'onNext' originally
throw new Exception("Throw exception manually for test purposes.");
})
.Subscribe(
_ => { /* nothing to do */ },
error =>
{
Console.WriteLine("OnError: {0}", error.Message);
}
);
Related Q&A : How to handle exceptions in OnNext when using ObserveOn?
(I don't think this is an exact duplicate, but a part overlaps with this question, IMO)
We're am trying to serialize processing of a list of business objects using a Saga.
Right now, without a Saga, we simply loop through a list of objects, and fire off a bus.Send(new ProcessBusinessObejct(obj)) async to have handlers execute. So the processing happens more or less in parallel, depending on this setting, I believe:
endpointConfiguration.LimitMessageProcessingConcurrencyTo( 4 );
This has worked fine but the amount of concurrent handlers is now hard on the database.
It would be OK to trigger these handlers in series, i.e. continue with the next only when the current process has finished (failed or succeeded). We don't want to set the concurrency to 1, it would affect all handlers in the endpoint.
The idea is to use the Scatter/Gather pattern and a Saga to keep track of the number of objects and update the state machine with the a count (total count, failed count, success count), and lastly fire an event when the list is done/empty.
The problem is
A) I'm not sure how to keep track of the list in the saga. The SagaData would need a List to keep all objects? Then remove an instance when a handler signals it's done processing.
The saga does not support hierarchical data and hence no List or List. I believe this is still the case in NSB v7.
And B) Is this use of saga feasable or overkill or is there a much simpler way to accomplish this?
We are using Sql Server persistence and transport and NSB 7.
Any input is much appreciated!
I think you are looking to do this. Mind you, depending on the persistence layer you are using, you might need to separate the actual import from updating the saga state. I have blogged about this here.
Saga data can also store a List, but I think in most of the scenarios you can get away with counts. Another important note (although it should be obvious) is that if a message fails to process and goes to the error queue (e.g. an uncaught exception in ImportData), the whole saga will be left incompleted until that message is retried and processed.
public class MySaga : Saga<MySagaData>
: IAmStartedByMessages<StartTheProcess>,
IHandleMessages<ImportData>,
IHandleMessages<ImportFinished>
{
public async Task Handle(StartTheProcess message, IMessageHandlerContext context)
{
Data.ObjectsToImport = message.ObjectCount;
Data.JobID = Guid.NewGuid(); //To generate a correlation ID to connect future messages back to this saga instance
foreach(var id in message.ObjectIdsToImport)
{
await context.SendLocal(new ImportData
{
JobID = Data.JobID //You need this to correlate messages back to the saga
//Anything else you need to pass on to ImportData
ObjectIdToImport = id
}
});
}
public async Task Handle(ImportData message, IMessageHandlerContext context)
{
//import the data and increment the counter
var result = ImportData(message.ObjectIdToImport);
if(result == Result.Success)
{
Data.SuccessImport++;
}
else
{
Data.FailedImport++;
}
await CheckIfFinished(context);
}
public async Task Handle(ImportFinished message, IMessageHandlerContext context)
{
//do any post cleanups or Mark as complete
MarkAsComplete();
return Task.CompletedTask;
}
private async Task CheckIfFinished(IMessageHandlerContext context)
{
if(Data.SuccessImport + Data.FailedImport == Data.ObjectsToImport)
{
//Everything is done
context.SendLocal(new ImportFinished { JobID = Data.JobID });
}
}
}