-
Notifications
You must be signed in to change notification settings - Fork 145
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
Use configured gson instance for toString #772
Use configured gson instance for toString #772
Conversation
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.
Overall, the patch looks reasonable to me.
There are a few stylistic nitpicks.
However, my main concern is the removal of toString()
methods from Message
and related classes.
This can create not only inconveniences while debugging, but also problems with logging.
Even in TracingMessageConsumer
itself, there are a number of places where messages are logged, which need to be changed in the same way as https://github.com/henryju/lsp4j/blob/bd47976a6a45957e5be9973e74196e1ce5e3f212/org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/TracingMessageConsumer.java#L176.
Basically, each of the log
calls in LSP4J needs to be analyzed, and amended if necessary. What's more, we'll need to ensure every time, both now and in the future, that there is an accessible instance of MessageJsonHandler
in every such case. What's even more, the existing clients that log messages using Message.toString()
would also be affected and would need to be changed in the same way as LSP4J itself.
I don't see a good solution here, but it seems that we need to restore those 'basic' toString()
methods, make sure they no longer throw exceptions, and try to use the new MessageJsonHandler.toString(Object)
method for message logging where it is possible (as, for example, in TracingMessageConsumer
itself).
WDYT?
org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/TracingMessageConsumer.java
Outdated
Show resolved
Hide resolved
org.eclipse.lsp4j.jsonrpc/src/test/java/org/eclipse/lsp4j/jsonrpc/test/IntegrationTest.java
Outdated
Show resolved
Hide resolved
org.eclipse.lsp4j.jsonrpc/src/test/java/org/eclipse/lsp4j/jsonrpc/test/PathTypeAdapter.java
Outdated
Show resolved
Hide resolved
org.eclipse.lsp4j.jsonrpc/src/test/java/org/eclipse/lsp4j/jsonrpc/test/PathTypeAdapter.java
Outdated
Show resolved
Hide resolved
org.eclipse.lsp4j.jsonrpc/src/test/java/org/eclipse/lsp4j/jsonrpc/test/PathTypeAdapter.java
Outdated
Show resolved
Hide resolved
String format = "[Trace - %s] Received request '%s - (%s)'\nParams: %s\n\n\n"; | ||
return String.format(format, date, method, id, paramsJson); | ||
} else if (message instanceof ResponseMessage) { | ||
ResponseMessage responseMessage = (ResponseMessage) message; | ||
String id = responseMessage.getId(); | ||
RequestMetadata requestMetadata = sentRequests.remove(id); | ||
if (requestMetadata == null) { | ||
LOG.log(WARNING, String.format("Unmatched response message: %s", message)); | ||
LOG.log(WARNING, String.format("Unmatched response message: %s", jsonHandler.toString(message))); |
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 same change needs to be done in all other places where messages are logged, potentially not only in this file. This is my main concern.
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 fixed the one I could find, but I agree there is a risk of someone still relying on the default toString
of a message. I will try to think about a better solution.
org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageJsonHandler.java
Outdated
Show resolved
Hide resolved
To illustrate, here is how the console log looks like after running the
|
org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Launcher.java
Outdated
Show resolved
Hide resolved
org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageJsonHandler.java
Show resolved
Hide resolved
bd47976
to
016ccdc
Compare
I fixed most of the feedback, except the point about having no more |
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.
Thank you very much for your work @henryju. I have left additional comments.
org.eclipse.lsp4j.jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Launcher.java
Show resolved
Hide resolved
@@ -72,12 +74,14 @@ public TracingMessageConsumer( | |||
Map<String, RequestMetadata> sentRequests, | |||
Map<String, RequestMetadata> receivedRequests, | |||
PrintWriter printWriter, | |||
MessageJsonHandler jsonHandler, |
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.
This is a breaking API change: a new parameter is added to the existing constructors. Also, when the constructor is called from the MessageTracer
, the jsonHandler
argument can now be null
. I'd suggest either to add a new constructor with the additional jsonHandler
parameter, or to add a setter like in MessageTracer
. In any case, if jsonHandler
is null
, the class needs to fallback to the old behaviour.
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.
By
class needs to fallback to the old behaviour.
do you mean it should create a new Gson instance and try to serialize it without custom adapters? I find this dangerous as it may still throw an exception as before.
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 think that in general we need to keep the old behavior as a fallback in cases where a MessageJsonHandler
is unavailable for whatever reason, but make sure that possible exceptions are properly handled and no longer thrown from those toString
methods. The goal is to avoid any breaking changes in API or behavior.
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.
To illustrate, WebSocketLauncherBuilder
s are currently broken, because they completely override the Builder.create()
method and we have not applied the messageTracer.setJsonHandler(jsonHandler)
change there. But such cases may also happen in existing clients. If there were a fallback strategy in TracingMessageConsumer
, they would not have been broken.
Similarly, even if it would be possible to inject a MessageJsonHandler
into each of the Message
objects when they are created inside the framework as you have suggested, we still need to have a 'baseline' behaviour in the Message.toString
method to avoid breaking existing clients that may create them outside the framework.
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 made another attempt. I didn't like the fact that the jsonHandler could be null on the MessageTracer
, so I went for a deprecation approach.
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 you ask me, I liked the previous approach more. To me, the problem looks like a 'progressive enhancement' of the old behaviour in cases where a MessageJsonHandler
is available. As I have mentioned, there might still be cases where it will not be available, so I can see no reason why we should require it in a constructor. This is my 'informed opinion' so to speak. Nevertheless, I really appreciate your effort.
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.
Since MessageTracer
constructor is package private, I take for granted that it is not instantiated/extended outside lsp4j. So changing it is fine.
Regarding TracingMessageConsumer
, I deprecated the old constructors, but keep them working as before using a fresh MessageJsonHandler
instance.
If you think the two use cases are valid on the long run, I can remove deprecation tags.
The last "issue" is the removal of toString
on the various messages. I don't like reverting to the old behavior, since this could be really creating bugs on user side. What about providing toString
with the best information we could, but not relying on gson serialization?
...c/src/test/java/org/eclipse/lsp4j/jsonrpc/test/ExtendableConcurrentMessageProcessorTest.java
Outdated
Show resolved
Hide resolved
....lsp4j.jsonrpc/src/test/java/org/eclipse/lsp4j/jsonrpc/test/json/MessageJsonHandlerTest.java
Outdated
Show resolved
Hide resolved
...cket.jakarta/src/main/java/org/eclipse/lsp4j/websocket/jakarta/WebSocketLauncherBuilder.java
Outdated
Show resolved
Hide resolved
...ipse.lsp4j.websocket/src/main/java/org/eclipse/lsp4j/websocket/WebSocketLauncherBuilder.java
Outdated
Show resolved
Hide resolved
8d89633
to
df463f8
Compare
private final Map<String, RequestMetadata> sentRequests = new HashMap<>(); | ||
private final Map<String, RequestMetadata> receivedRequests = new HashMap<>(); | ||
|
||
MessageTracer(PrintWriter printWriter) { | ||
MessageTracer(PrintWriter printWriter, MessageJsonHandler jsonHandler) { |
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.
This is also a breaking API 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 constructor is package private, so I would say this is not a breaking API 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.
My bad. You are right, of course. But I'd still prefer a setter in this case.
05b6708
to
6cd0673
Compare
To sum up and hopefully clarify, here is what I'd like to see in this PR. Thanks to the prompt by @szarnekow and the following changes by @henryju, I have been persuaded that there is a clean non-breaking solution to this problem. So, one of the main goals should be to preserve all of the existing API elements and augment the existing behaviour rather than trying to replace it. To put it in a perspective, there were no issues reported about this problem since 2019 when the Therefore, let's keep the current API and implementation to avoid breaking any of the existing clients, and just augment it with the enhanced implementation where it is possible, using the old one as a fallback. To that end, let's first reinstate all of the removed methods and their behaviour, including the static Let's rename the (new) instance Let's use a setter for passing a When no That would be a clean non-breaking solution I'd like to see. As a bonus, let's make sure that possible exceptions are properly handled in the existing static However, I'd consider this bonus part as optional for this PR. |
I won't say I am super convinced by keeping dangerous |
On one hand, at least it will not make things any worse for existing clients. With your current proposal, clients might start to see something like
instead of
I don't think it would be fine for most purposes. On the other hand, we can try to make the 'dangerous'
|
@@ -294,7 +299,7 @@ public Builder<T> validateMessages(boolean validate) { | |||
|
|||
public Builder<T> traceMessages(PrintWriter tracer) { | |||
if (tracer != null) { | |||
this.messageTracer = new MessageTracer(tracer); |
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.
With your current approach, we'd still need to keep this line with the one-arg constructor as a fallback. Otherwise, an existing subclass that completely overrides Builder.create()
would have no messageTracer
set, which is a breaking change. Similarly, we'd need to make the new two-arg constructor public so that such subclasses could reset the default messageTracer
in their create()
implementations. That's why I much prefer your previous approach with MessageTracer.setJsonHandler
. It is so much less of a hassle.
For the sake of convenience, I have pushed what I'd consider a clean patch against If you don't mind, let's use it as a baseline for further discussion and development. |
Let's see if I can come up with something along the lines sketched out in #768 (comment) to make the 'dangerous' |
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.
Note that I had to copy ToStringBuilder
from the org.eclipse.lsp4j.util
package as it could not be accessed from here. I also had to add a couple of new methods: addAllFields(Predicate<Field>)
, which is called from Message.toStringFallback()
, and, for the sake of symmetry, addDeclaredFields(Predicate<Field>)
.
I don't think it is good for us to have three copies of ToStringBuilder
in LSP4J now.
It seems that the best solution would be to extract a separate bundle such as org.eclipse.lsp4j.common
and move ToStringBuilder
there, perhaps with some other common stuff, so that it could be accessed from everywhere.
However, I'd consider that to be out of the scope of this PR.
In the meantime, I think we'll have to live with yet another copy of ToStringBuilder
here as a package-private class. At least, it is not API and can be safely removed at any time.
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.
As lsp4j.jsonrpc is at the root of all dependency chains I think having it just in this new place is fine. And it is certainly fine to defer to a future PR
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'll leave it to a future PR to keep this one free of the associated noise.
/** | ||
* A general message as defined by JSON-RPC. The language server protocol always | ||
* uses "2.0" as the jsonrpc version. | ||
*/ | ||
public abstract class Message { | ||
|
||
private transient MessageJsonHandler jsonHandler; |
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 not entirely comfortable with this idea of providing a way to inject a MessageJsonHandler
into a Message
and storing it in a transient field, as it can potentially break existing reflective code that enumerates/introspects message properties using fields rather than JavaBeans-properties.
Note that I had to rename Message.getJsonHandler()
to jsonHandler()
previously so that ReflectiveMessageValidator
did not actually fail by treating jsonHandler
as a general property of the message, which seems to indicate that it might be more than a hypothetical issue.
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 am not really concerned by this - but I have added an entry to the Changelog that mentions what to do in this case.
@@ -119,6 +121,8 @@ public Message parseMessage(Reader input) throws JsonParseException { | |||
Message message = gson.fromJson(jsonReader, Message.class); | |||
|
|||
if (message != null) { | |||
message.setJsonHandler(this); |
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.
This call might seem unnecessary, since we already set the MessageJsonHandler
in MessageTypeAdapter
and DebugMessageTypeAdapter
as soon as a message is created, but it serves those clients that register their own message type adapter (similarly to how DebugMessageTypeAdapter
is registered), but don't set the MessageJsonHandler
for created messages.
@@ -74,6 +74,7 @@ private static ResponseError fallbackResponseError(String header, Throwable thro | |||
private final MessageConsumer out; | |||
private final Endpoint localEndpoint; | |||
private final Function<Throwable, ResponseError> exceptionHandler; | |||
private MessageJsonHandler jsonHandler; |
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.
Note that RemoteEndpoint
now has the reference to a MessageJsonHandler
to be able to inject the handler into the messages it creates.
@jonahgraham Could you please review this PR with a fresh pair of eyes? We tried to enhance the existing implementation without any breaking changes by passing a specific If all else fails, As far as I can see, the main potential issue with the current patch is that existing reflective code might be broken by the addition of the |
Regarding the potential issue with existing reflective code, I now think that it would probably be sufficient to just mention it in the CHANGELOG as a potential breaking change, but I would still appreciate an extra pair of eyes to review this PR. It would be great if we could merge it before the 0.22.0 release. |
I added it to the 0.22.0 milestone. It fell off the bottom of my todo list, I will schedule to look at it sometime before the 0.22.0 release. See conversation about timing in #732 |
OK. Thank you! |
This is important when users have registered custom type adapters Fixes eclipse-lsp4j#768
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.
This looks fine to me. I am about to push a rebased squashed version with the above mentioned changelog entry too to ensure that it builds cleanly.
e8ab10c
to
03a4a14
Compare
next *week :-) |
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.
@jonahgraham Thank you very much for your review and approval.
May I suggest a couple of minor enhancements to the changelog entry?
Co-authored-by: Vladimir Piskarev <[email protected]>
Thanks everyone. This is now committed and the next release of LSP4J (expected next week) will contain this improvement. |
This is important when users have registered custom type adapters
Fixes #768