how to implement a stateless listener (XDebugSessionListener/XDebuggerManagerListener)
Hello Community,
The problem I am trying to solve
In my Python plugin I want to create an XDebugSessionListener for every Python debug session. So that I can evaluate some information (e.g. version of an installed Python package) and store the result in project.putUserData. This information is necessary to decide whether some of my debug-actions can be activated in the debugger tool window. The evaluation of this data can't be done inside the AnAction::update where I have to decide if my action is visible or not.
My current PoC seem to work as expected. But my code violates at least one rule which is described here: plugin-listeners
Listener implementations must be stateless and may not implement life-cycle (e.g., Disposable).
My current implementation:
plugin.xml
<projectListeners>
<listener class="MyDebuggerListener"
topic="com.intellij.xdebugger.XDebuggerManagerListener"
/>
</projectListeners>
MyDebuggerListener (Kotlin code):
import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebuggerManagerListener
import com.jetbrains.python.debugger.PyDebugProcess
class MyDebuggerListener : XDebuggerManagerListener {
private var currentSessionListener: MyDebugSessionListener? = null
override fun currentSessionChanged(previousSession: XDebugSession?, currentSession: XDebugSession?) {
if (previousSession != null) {
currentSessionListener?.let {
currentSessionListener = null
previousSession.removeSessionListener(it)
}
}
if (currentSession?.debugProcess is PyDebugProcess) {
// MyDebugSessionListener needs the `currentSession` to evaluate a simple expression as soon as the session is paused.
currentSessionListener = MyDebugSessionListener(currentSession).also {
currentSession.addSessionListener(it)
}
}
}
}
My questions are:
1) What would be the correct way to clean up an XDebugSessionListener (MyDebugSessionListener in my code example) when it isn't allowed to store a reference to the last registered one inside the used XDebuggerManagerListener?
A possible solution should also take into account, that an active XDebugSessionListener instance has to be removed in case the user unloads my plugin.
2) What generally happens to active projectListeners instances, defined in the plugin.xml, when a user unloads a plugin? Are these listeners automatically unsubscribed?
请先登录再写评论。
I noticed today that a user can start more than one debug process. Therefore I need to evaluate the version for each of the started debug processes.
My updated code now looks like this:
plugin.xml (simplified)
Code:
This solves my problem described in Question 1, listener are now stateless, except that EvalVersionDebugSessionListener requires the session to evaluate the info.
Is this approach OK or is there something simpler to remove listeners that are no longer needed?
> The evaluation of this data can't be done inside the AnAction::update where I have to decide if my action is visible or not.
Could you please provide more details on why this is the case?
Hi Yann,
the evaluation, if a specific Python module is installed, takes some time and can't be done inside the EDT. So it is done in another thread. But the update method of my action is not called again after I got the result.
Here is a minimal example (without proper error handling) to demo the problem:
plugin.xml (simplified)
the action:
I test my action with a simple Python code like this one:
When the debugger hits the breakpoint I select the variable a in the debugger tool window and open the context menu (right click on a). If everything would work the context menu should include the entry Open Custom Dialog.
I guess I found an acceptable way to solve my problems. Instead of storing the debug sessions in a map, I now use some kind of fingerprint to store my information. This helps to reduce the risk of potential memory leaks.
From my side, all questions are answered or no longer relevant.
Here is my current solution:
plugin.xml (simplified)
The code:
Hi, you can try using BGT action update thread in your action: https://plugins.jetbrains.com/docs/intellij/basic-action-system.html#principal-implementation-overrides and do the logic right in the action update
Hello Egor,
that could actually be a good solution. At the moment I still support older IntelliJ versions, so I can't use this solution yet. But I will try it out as soon as I drop support for the old versions in my plugin.
Thank you for letting me know.
I was able to try it out today and it works perfectly (so much simpler now).
Thank you Egor Ushakov for the tip.
Glad to hear that!