Repository: spyder-ide/spyder

Issue:
Spyder using  increasingly more CPU & memory while idling when using the Tkinter backend
<!--- **PLEASE READ:** When submitting here, please ensure you've completed the following checklist and checked the boxes to confirm. Issue reports without it may be closed. Thanks! --->

### Issue Report Checklist

* [x] Searched the [issues page](https://github.com/spyder-ide/spyder/issues?q=is%3Aissue) for similar reports
* [x] Read the relevant sections of the [Spyder Troubleshooting Guide](https://github.com/spyder-ide/spyder/wiki/Troubleshooting-Guide-and-FAQ) and followed its advice
* [x] Reproduced the issue after updating with ``conda update spyder`` (or ``pip``, if not using Anaconda)
* [ ] Could not reproduce inside ``jupyter qtconsole`` (if console-related)
* [ ] Tried basic troubleshooting (if a bug/error)
    * [ ] Restarted Spyder
    * [ ] Reset preferences with ``spyder --reset``
    * [ ] Reinstalled the latest version of [Anaconda](https://www.anaconda.com/download/)
    * [ ] Tried the other applicable steps from the Troubleshooting Guide
* [ ] Completed the **Problem Description**, **Steps to Reproduce** and **Version** sections below


## Problem Description
I am using Spyder 5.3.3 on WSL2 Ubuntu 20.04, installed via pip.
Just having Spyder sitting idle for a while leads to increasing CPU & memory use, eventually reaching 100% of CPU.

![image](https://user-images.githubusercontent.com/14614249/196300967-53915d73-0919-4225-8c3b-4ab8b7b98213.png)


### What steps reproduce the problem?

1.  Start spyder
2.  wait
3. 

### What is the expected output? What do you see instead?



### Paste Traceback/Error Below (if applicable)
<!--- Copy from error dialog or View > Panes > Internal Console --->

```python-traceback

PASTE TRACEBACK HERE

```

## Versions
<!--- You can get this information from Help > About Spyder...
or (if Spyder won't launch) the "conda list" command
from the Anaconda Prompt/Terminal/command line. --->

* Spyder version: 5.3.3
* Python version: 3.8.10 IPython 7.31.1
* Qt version: 
* PyQt version: 
* Operating System name/version: WSL2 Ubuntu 20.04 on Windows

### Dependencies
<!--- Please go to the menu entry Help > Dependencies,
press the Copy to clipboard button and paste below --->

```
PASTE DEPENDENCIES HERE
```



Discussion:
Hey @fdarvas, thanks for reporting. As you can see from your screenshot, the problem is not with Spyder (which consumes very little memory). It has to do with an external process that seems created by Spyder.

So please post the full command of that `python3` process that is consuming a lot of memory in your PC to see what is related to.
There is no external process - If I start spyder from the terminal with `spyder`, this python3 process show up in top and starts accumulating memory and CPU time. 

When I close spyder, the process goes away too. 
So I presume it is started along with spyder?

I see this , even if dont run any code inside spyder.

> There is no external process

We open several external processes when Spyder starts, e.g. to run code and provide code completion in the editor. That's why that process is not labeled as `spyder` in the `top` screenshot you posted, but as `python3`. And this is for you to understand that the bug you're reporting is not related directly to Spyder but to that external process.

Furthermore, that's the reason why we need the full command of that process, so we understand which external process is causing the memory leak in your machine. So, instead of arguing with me about it, please get that command from `top` (by pressing `c` after it starts) and post it here.
I thought community feedback was actually a valuable and welcome process to make this software better. Sorry to have apparently irritated you, so will stop that right here. 


Sorry, I apologize, I know I was harsh. If you're able to provide the piece of info I asked for, then we could continue seeing what your problem is about.

If you don't feel comfortable with doing it, I can only let you know that this bug seems very similar to issue #19026, which should be fixed in our next version (5.4.0), to be released next week. You can also find there more info about how to solve this problem for now.
No problem, apology accepted.  - Some of us are just users, and so not necessarily always deep diving into the problem right away - I was just hoping to get this kind of dialogue, by providing the initial information. I actually enjoy Spyder quite a bit, so naturally have an interest in getting this sorted out.

I did a reset to spyder factory defaults, which seems to have fixed the problem (unclear how and why). Possibly because the graphics backend changed? 
It is back to inline. 
Reading through that other thread I suspect it could be the same issue.  

Switching from inline to Tkinter, brings the problem back. So I think it is the same issue.

The python3 process in top is this one I believe:

8971 felix     20   0 3501796 217088  60740 S   0.7   0.3   0:07.51 /usr/bin/python3 -m spyder_kernels.console -f /home/felix/.local/share/jupyter/runtime/kernel-619aec1fb775.json




> Switching from inline to Tkinter, brings the problem back. So I think it is the same issue.

Right, but the thing is that we fixed the memory leak issue only for the Automatic/Qt backends, not for Tkinter. So, I'll reopen your issue to see if we can fix it in a later version (we're very close to release the next one).

> The python3 process in top is this one I believe

Thanks! That confirms that this bug happens in the process we use to run code, which is precisely the one where we set the Tkinter backend.
@impact27, perhaps you have some free time to take a look at this one? I think it only happens on Linux, but it could also occur on Mac.
It is probably a leak in https://github.com/ipython/ipykernel that is triggered each time a comm message comes.
fixed by https://github.com/ipython/ipykernel/pull/1008
installing the ipykernel from source seems to have fixed the issue.
Fantastic! Thanks a lot @fdarvas for the confirmation and for your patience in dealing with me. I'm glad everything turned out well in the end.

We'll make sure Spyder picks up the version of IPykernel that has the fix for this in our next version.

Resolving patch:
diff --git a/external-deps/spyder-kernels/setup.py b/external-deps/spyder-kernels/setup.py
--- a/external-deps/spyder-kernels/setup.py
+++ b/external-deps/spyder-kernels/setup.py
@@ -40,7 +40,7 @@ def get_version(module='spyder_kernels'):
     'backports.functools-lru-cache; python_version<"3"',
     'cloudpickle',
     'ipykernel<5; python_version<"3"',
-    'ipykernel>=6.15.3,<7; python_version>="3"',
+    'ipykernel>=6.16.1,<7; python_version>="3"',
     'ipython<6; python_version<"3"',
     'ipython>=7.31.1,<8; python_version>="3"',
     'jupyter-client>=5.3.4,<6; python_version<"3"',
diff --git a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py
--- a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py
+++ b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py
@@ -20,6 +20,7 @@
 # Third-party imports
 import ipykernel
 from ipykernel.ipkernel import IPythonKernel
+from ipykernel import eventloops
 from traitlets.config.loader import LazyConfigValue
 
 # Local imports
@@ -398,48 +399,43 @@ def get_mpl_interactive_backend(self):
         """
         # Mapping from frameworks to backend names.
         mapping = {
-            'qt': 'QtAgg',  # For Matplotlib 3.5+
-            'qt5': 'Qt5Agg',
+            'qt': 'QtAgg',
             'tk': 'TkAgg',
             'macosx': 'MacOSX'
         }
 
-        try:
-            # --- Get interactive framework
-            framework = None
-
-            # This is necessary because _get_running_interactive_framework
-            # can't detect Tk in a Jupyter kernel.
-            if hasattr(self, 'app_wrapper'):
-                if hasattr(self.app_wrapper, 'app'):
-                    import tkinter
-                    if isinstance(self.app_wrapper.app, tkinter.Tk):
-                        framework = 'tk'
-
-            if framework is None:
-                try:
-                    # This is necessary for Matplotlib 3.3.0+
-                    from matplotlib import cbook
-                    framework = cbook._get_running_interactive_framework()
-                except AttributeError:
-                    # For older versions
-                    from matplotlib import backends
-                    framework = backends._get_running_interactive_framework()
-
-            # --- Return backend according to framework
-            if framework is None:
-                # Since no interactive backend has been set yet, this is
-                # equivalent to having the inline one.
-                return 0
-            elif framework in mapping:
-                return MPL_BACKENDS_TO_SPYDER[mapping[framework]]
+        # --- Get interactive framework
+        framework = None
+
+        # Detect if there is a graphical framework running by checking the
+        # eventloop function attached to the kernel.eventloop attribute (see
+        # `ipykernel.eventloops.enable_gui` for context).
+        from IPython.core.getipython import get_ipython
+        loop_func = get_ipython().kernel.eventloop
+
+        if loop_func is not None:
+            if loop_func == eventloops.loop_tk:
+                framework = 'tk'
+            elif loop_func == eventloops.loop_qt5:
+                framework = 'qt'
+            elif loop_func == eventloops.loop_cocoa:
+                framework = 'macosx'
             else:
-                # This covers the case of other backends (e.g. Wx or Gtk)
-                # which users can set interactively with the %matplotlib
-                # magic but not through our Preferences.
-                return -1
-        except Exception:
-            return None
+                # Spyder doesn't handle other backends
+                framework = 'other'
+
+        # --- Return backend according to framework
+        if framework is None:
+            # Since no interactive backend has been set yet, this is
+            # equivalent to having the inline one.
+            return 0
+        elif framework in mapping:
+            return MPL_BACKENDS_TO_SPYDER[mapping[framework]]
+        else:
+            # This covers the case of other backends (e.g. Wx or Gtk)
+            # which users can set interactively with the %matplotlib
+            # magic but not through our Preferences.
+            return -1
 
     def set_matplotlib_backend(self, backend, pylab=False):
         """Set matplotlib backend given a Spyder backend option."""


Test patch: