-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Track start/end template + before/after node resolve #48693
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
base: main
Are you sure you want to change the base?
Conversation
cf41f2c
to
1768462
Compare
@mkouba @maxandersen @ia3andy please review this PR because I need it to provide Qute debugger which starts working. |
@FroMage please also review this PR |
That looks very cool, as this is based on the contract that will be hard to change, we need to make sure the api/contract is flexible and will not change in the future. |
Perhaps I will need more changes because for the moment my Qute debugger is just a POC and I have hard coded some things like src/main/resources/templates folder because I need to retrieve file path from a given template id since Qute works only with template id and not with file path. But I think the API to track nodes is enough. |
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.
LGTM, though I'm not a fan of the nested interface for the service file, we should probably move it out.
But I'll let @mkouba merge this.
@@ -58,6 +59,11 @@ public final class EngineBuilder { | |||
this.timeout = 10_000; | |||
this.useAsyncTimeout = true; | |||
this.listeners = new ArrayList<>(); | |||
// Load EngineListener implementations via Java SPI and register them. | |||
ServiceLoader<EngineListener> listenerExtensions = ServiceLoader.load(EngineListener.class); |
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.
Apparently this is an inner class, making it hard to add a services file for it, because EngineBuilder$EngineListener
is often an annoying character to deal with from the command-line.
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 know but I don't want to change the existing API.
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.
Why do we use the service loader at all? I mean, we don't use service loaders for anything else. The EngineBuilder
is intended to be used purely programmatically.
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 need this service loader for the Qute debuger. The Qute debugger need to track all Engine which was built. If your Quarkus App uses for instance N EngineBuilder, the Qute debugger can track all engine created by the N EngineBuilder.
By using ServiceLoader, the Qute core have no dependencies to the Qute debugger.
independent-projects/qute/core/src/main/java/io/quarkus/qute/EngineImpl.java
Show resolved
Hide resolved
private static CompletionStage<ResultNode> resolveWith(TemplateNode templateNode, ResolutionContext context) { | ||
private static CompletionStage<ResultNode> resolveWith(TemplateNode templateNode, ResolutionContext context, | ||
Engine engine) { | ||
if (!engine.hasTraceListeners()) { |
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.
Oh that's where it's used, OK.
I agree with you but it is the existing API. |
@mkouba I switched the PR to draft because I have some bugs with item inside #for while debugging. I don't know if the problem comes from this PR. |
@mkouba sorry for the noise, the bug was inside my current Qute debugger, this PR works perfectly for now. |
@Override | ||
public TraceManager getTraceManager() { | ||
if (traceManager == null) { | ||
traceManager = new TraceManager(); |
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.
Why the lazy loading here? Also you would need use the volatile
modifier + synchronize, or the io.quarkus.qute.LazyValue
abstraction to make it thread-safe.
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 wanted to create a trace manager if nobody regsiters a listener, but I agree with you, I can create it every time and set it as final, since it exists hasTraceListeners.
*/ | ||
public class TraceManager { | ||
|
||
private List<TraceListener> listeners; |
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.
You should use a thread-safe List
variant (such as CopyOnWriteArrayList
). And ideally, the reference should be final
.
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.
Indeed it is very basic, I need to improve that but for the moment I have not seen some trouble.
Signed-off-by: azerr <azerr@redhat.com>
This PR provides the capability to track start/end template rendering and before/after node resolve (it follows the same idea than Saxon events used to implement a debugger). I need this support to provide the Qute debugger that I am implementing and integrating in IntelliJ (and it will be easy to integrate in vscode and even in Eclipse IDE):
Here a demo with breakpoint and steps:
Here a demo with expression eval:
Breakpoint with condition is supported:
Engine#addTraceListener(TraceListener listener)
The track can be done by adding a TraceListener registered with
Engine#addTraceListener(TraceListener listener)
Here a sample how to use TraceListener:
You will see in the console the following trace:
Register TraceListener with Java SPI
As you can see the PR have no dependencies to the Qute debugger. How Qute debugger can be registered?
The answer is that Qute debugger will track creation of Engine instance with the existing EngineListener API. Qute debugger need to register a custom EngineListener (RegisterDebugServerAdapter) to track Engine build. To do that the Qute debugger register this custom EngineListener with Java SPI with the following file stored in the Qute debugger artifact
src/main/resources/META-INF/services/io.quarkus.qute.EngineBuilder$EngineListener
which containsio.quarkus.qute.debug.adapter.RegisterDebugServerAdapter
The Qute debugger adds a custom TraceListener to block the process (by using wait() in onAfterResolve if there is a breakpoint defined for the resolved node.