-
Notifications
You must be signed in to change notification settings - Fork 352
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
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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
@@ -287,6 +287,12 @@ void IODataOutputInStreamErrorListener.OnInStreamError() | |||
this.inStreamErrorListener.OnInStreamError(); | |||
} | |||
|
|||
/// <inheritdoc/> | |||
Task IODataOutputInStreamErrorListener.OnInStreamErrorAsync() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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
IODataOutputInStreamErrorListener
interface 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
IODataOutputInStreamErrorListener
interface 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
.ODataBatchWriter
is apublic abstract
class that implements theIODataOutputInStreamErrorListener
interface. IfOnInStreamErrorAsync
method was implemented abstractly in the classes, subclasses ofODataBatchWriter
that users of the library might have implemented would break. For that reason, the method has been implemented as a virtual method that just throws aNotImplementedException
to prevent the breaking change.As for other
internal
classes 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
/ODataJsonLightWriter
ODataParameterWriterCore
/ODataJsonLightParameterWriter
ODataCollectionWriterCore
/ODataJsonLightCollectionWriter
(PR Implement asynchronous support in ODataJsonLightCollectionWriter #2059)ODataDeltaWriter
/ODataJsonLightDeltaWriter
ODataBatchWriter
/ODataJsonLightBatchWriter
ODataAsynchronousWriter
Below is how that implementation looks like in
ODataCollectionWriterCore
. Notice the call to the asynchronous methodStartPayloadInStartStateAsync
that 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.