Repository navigation
Modify IODataOutputInStreamErrorListener interface to support asynchronous notifications when an in-stream error is to be written - #2058
Conversation
fbcb2c4 to
7eab95c
Compare
5ce68ae to
389da7f
Compare
| { | ||
| // NOTE: ODataBatchWriter class is abstract and public. This method body is intended | ||
| // to prevent a breaking change in subclasses that provide the implementation. | ||
| throw new NotImplementedException(); |
There was a problem hiding this comment.
I wonder if wrapping OnInStreamError in a task would be a more "expected" default implementation for OnInStreamErrorAsync. Is that something you considered? If so, I would be curious to know what dissuaded you from that approach.
Also does the method body prevent breaking change in subclasses that provide the implementation or in those that did not provide the implementation?
There was a problem hiding this comment.
@habbes Dealing with asynchronous wrappers over synchronous methods is actually what this "true async" initiative is all about so I don't think its a good idea to wrap OnInStreamError.
With regard to your second point, the method body prevents a breaking change in the event that someone had subclassed ODataBatchWriter. The only implementation they would have provided is for the synchronous method and we are not tinkering with that.
There was a problem hiding this comment.
@gathogojr on the first point: You make a good point. But I still wonder which default is the lesser evil. My concern is whether there cases where OnInStreamErrorAsync is called without the knowledge or control of the developer who implemented the abstract class. I think this is usually quite likely. If this is the case, then merely updating the library without any additional changes to their program code might lead to unexpected exceptions occurring (possibly occurring in production and undetected in development). This would be quite disruptive, and a breaking change. On the other hand, if the default implementation wraps the OnInStreamError method, then program will retain its current behaviour even after updating the lib. While this implementation is not necessarily ideal for async, it's less disruptive and not a breaking change. If you look at the case of the built-in Stream abstract class, Flush is a abstract and FlushAsync is a virtual method which defaults to wrapping Flush in a task: https://source.dot.net/#System.Private.CoreLib/Stream.cs,189
There was a problem hiding this comment.
If we stick with the default of throwing an exception, then I think we should have a way to ensure that developers updating the library in their programs (which could also be libraries used by others) are aware that they need to implement that method or to ensure that that method is not inadvertently called internally by ODL.
There was a problem hiding this comment.
@habbes I think you misunderstood. The OnStreamInErrorAsync is not triggered automatically, just like the current OnStreamInError is not triggered automatically. Just adding the method does not cause it to be triggered.
Here is how the existing method is currently triggered.
Which is why throwing a NotImplementedException does no harm. FYI, most of the public virtual methods in the public abstract ODataWriter actually throw NotImplementedException. If as a developer you choose to provide an implementation, you'll need to implement the methods that you intend to trigger. It's no different here.
There was a problem hiding this comment.
If a developer updates the library in their programs, the method will just sit there doing nothing. It'll not be triggered at any point.
There was a problem hiding this comment.
@gathogojr I'm a bit confused. Are those methods triggered by the developer's code or by ODL code?
In the link you provided, the OnStreamInError method seems to be explicitly called by ODL code, so that leaves me a bit confused by what you mean when you say "just like the current OnStreamInError is not triggered automatically. In the PR changes, I can't see an instance where OnStreamInErrorAsync is called, who is responsible for calling it and where? If it's never called directly by ODL, then what's the point of having that method on the class? I feel like I'm missing something.
There was a problem hiding this comment.
@habbes I'll rephrase. The breaking change concern stems from the scenario where a developer has implemented the ODataBatchWriter abstract class. Let's say they call it DeveloperODataBatchWriter. What I meant by saying it is not triggered automatically is that, ODL wouldn't trigger DeveloperODataBatchWriter.OnStreamInErrorAsync at any time. The link I shared is to ODL internal ODataJsonLightOutputContext triggering the method. What I could check is if this internal class can resolve ODataBatchWriter from the DI container before triggering the method because that would be the only way it'd be a breaking change.
There was a problem hiding this comment.
The other concern I have with your suggestion is that the synchronous and asynchronous approach are using 2 different writers. While they share the same output stream, we verify every step of the way whether the OutputContext instance if set for synchronous or asynchronous writing
There was a problem hiding this comment.
@gathogojr and I had an offline chat around this topic and he clarified why this is not a breaking change. Thought I'd share that here as well.
The ODL calls OnInStreamError for the internal implementations of the OutputContext class. This internal implementations only uses internal subclasses of ODataBatchWriter. At no point does it use an ODataBatchWriter that would be passed in by third-party code or via external-facing dependency-injection. So if the user creates their own subclass of the ODataBatchWriter, then we can guarantee that that version will not be called by the internal ODL methods. The only exception is if the user also creates their own OutputContext class, but if they do this, then they are responsible for all the logic, including error handling, whether or not to use error listeners, how to use invoke error listeners, etc. So they will cater for all that (note that a lot of the built-in classes, as well as the IODataOutputInStreamErrorListener interface are internal and not accessible to the developer).
A follow-up question I had was why it was necessary to add the OnStreamInErrorAsync method if it's never called by ODL. The reason it was necessary is because ODataBatchWriter implements IODataOutputInStreamErrorListener and this method has been added to that interface, so either ODataBatchWriter or its concrete subclasses need to implement it. So a basic implementation is added to ODataBatchWriter so that subclasses aren't required to.
389da7f to
c2db8d4
Compare
| } | ||
|
|
||
|
/// |
||
| Task IODataOutputInStreamErrorListener.OnInStreamErrorAsync() |
There was a problem hiding this comment.
Out of curiosity, why is this method explicitly defined for the IODataOutputInStreamErrorListener interface? I realize that this is also the case for the pre-existing OnInStreamError methods, but just curious to know why. Does this class implement multiple interfaces each defining OnInStreamError?
There was a problem hiding this comment.
Does this class implement multiple interfaces each defining OnInStreamError?
No it doesn't
why is this method explicitly defined for the IODataOutputInStreamErrorListener interface?
I'm not 100% sure why explicit interface implementation route was taken
There was a problem hiding this comment.
I'm guessing the benefit is that any class that implements ODataBatchWriter abstract class for example can override the default IODataOutputInStreamErrorListener methods either as a public method, or as an explicit interface implementation.
There was a problem hiding this comment.
@habbes A person who implements ODataBatchWriter abstract class would only bring it into play by implementing the public abstract ODataOutputContext CreateODataBatchWriter method just like we do in ODataJsonLightOutputContext.
By doing that they take the internal ODataJsonLightOutputContext out of play and take the responsibility of triggering the IODataOutputInStreamErrorListener methods.
ODataJsonLightOutputContext has a hard dependency on ODataJsonLightBatchWriter as you can see here
private ODataBatchWriter CreateODataBatchWriterImplementation()
{
ODataBatchWriter batchWriter = new ODataJsonLightBatchWriter(this);
this.outputInStreamErrorListener = batchWriter;
return batchWriter;
}…onous notifications when an in-stream error is to be written
c2db8d4 to
666fe7e
Compare
|
This PR has Quantification details
Why proper sizing of changes matters
Optimal pull request sizes drive a better predictable PR flow as they strike a
What can I do to optimize my changes
How to interpret the change counts in git diff output
Was this comment helpful? 👍 :ok_hand: :thumbsdown: (Email) |
Issues
This pull request is in partial fulfilment of issue #2019.
Description
Modify
IODataOutputInStreamErrorListenerinterface to support asynchronous notifications when an in-stream error is to be writtenBackground
When an error occurs when writing a payload to the stream, a notification is sent to any subscribed listener to notify it that an in-stream error is to be written. The listener implements the
IODataOutputInStreamErrorListenerinterface that contains a single synchronous methodOnInStreamError. This interface isinternal.To support similar functionality in asynchronous scenarios, we need to modify the interface to support an asynchronous equivalent method -
OnInStreamErrorAsync.ODataBatchWriteris apublic abstractclass that implements theIODataOutputInStreamErrorListenerinterface. IfOnInStreamErrorAsyncmethod was implemented abstractly in the classes, subclasses ofODataBatchWriterthat users of the library might have implemented would break. For that reason, the method has been implemented as a virtual method that just throws aNotImplementedExceptionto prevent the breaking change.As for other
internalclasses that implement the interface, an explicit implementation has been added. For now, it just throws aNotImplementedException. The actual implemententation will be provided when implementing asynchronous support in the following 6 sets of writer classes:ODataWriterCore/ODataJsonLightWriterODataParameterWriterCore/ODataJsonLightParameterWriterODataCollectionWriterCore/ODataJsonLightCollectionWriter(PR Implement asynchronous support in ODataJsonLightCollectionWriter #2059)ODataDeltaWriter/ODataJsonLightDeltaWriterODataBatchWriter/ODataJsonLightBatchWriterODataAsynchronousWriterBelow is how that implementation looks like in
ODataCollectionWriterCore. Notice the call to the asynchronous methodStartPayloadInStartStateAsyncthat sets the stage for writing the error.Checklist (Uncheck if it is not completed)
Additional work necessary
If documentation update is needed, please add "Docs Needed" label to the issue and provide details about the required document change in the issue.