Repository: spyder-ide/spyder

Issue:
AttributeError: 'WriteWrapper' object has no attribute '_thread_id' in kernel
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

I am writing this code and occur this error. Not sure what's going on.

import networkx as nx
G = nx.DiGraph(nx.path_graph(4))
nx.pagerank(G)

### Traceback
```python-traceback
Exception in comms call get_namespace_view:
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/comms/commbase.py", line 343, in _handle_remote_call
    return_value = self._remote_callback(
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/comms/frontendcomm.py", line 270, in _remote_callback
    return super(FrontendComm, self)._remote_callback(
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/comms/commbase.py", line 356, in _remote_callback
    return self._remote_call_handlers[call_name](
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/console/kernel.py", line 194, in get_namespace_view
    view = make_remote_view(ns, settings, EXCLUDED_NAMES)
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/utils/nsview.py", line 686, in make_remote_view
    view = value_to_display(value, minmax=settings['minmax'])
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/utils/nsview.py", line 443, in value_to_display
    np.set_printoptions(**np_printoptions)
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py", line 252, in set_printoptions
    array([-0., -0.,  0.,  0.])
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/numpy/core/arrayprint.py", line 94, in _make_options_dict
    warnings.warn(
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/warnings.py", line 112, in _showwarnmsg
    _showwarnmsg_impl(msg)
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/warnings.py", line 30, in _showwarnmsg_impl
    file.write(text)
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/comms/frontendcomm.py", line 310, in __call__
    self._write(
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/comms/frontendcomm.py", line 300, in __call__
    if self._thread_id != threading.get_ident():
AttributeError: 'WriteWrapper' object has no attribute '_thread_id'
```

## Versions

* Spyder version: 5.2.2 
* Python version: 3.9.12
* Qt version: 5.9.7
* PyQt5 version: 5.9.2
* Operating System: Darwin 21.6.0

### Dependencies

```
# Mandatory:
applaunchservices >=0.1.7     :  0.3.0 (OK)
atomicwrites >=1.2.0          :  1.4.0 (OK)
chardet >=2.0.0               :  4.0.0 (OK)
cloudpickle >=0.5.0           :  2.0.0 (OK)
cookiecutter >=1.6.0          :  1.7.3 (OK)
diff_match_patch >=20181111   :  20200713 (OK)
intervaltree >=3.0.2          :  3.1.0 (OK)
IPython >=7.6.0;<8.0.0        :  7.31.1 (OK)
jedi >=0.17.2;<0.19.0         :  0.18.1 (OK)
jellyfish >=0.7               :  0.9.0 (OK)
jsonschema >=3.2.0            :  4.4.0 (OK)
keyring >=17.0.0              :  23.4.0 (OK)
nbconvert >=4.0               :  6.4.4 (OK)
numpydoc >=0.6.0              :  1.4.0 (OK)
parso >=0.7.0;<0.9.0          :  0.8.3 (OK)
pexpect >=4.4.0               :  4.8.0 (OK)
pickleshare >=0.4             :  0.7.5 (OK)
psutil >=5.3                  :  5.9.0 (OK)
pygments >=2.0                :  2.11.2 (OK)
pylint >=2.5.0                :  2.9.6 (OK)
pyls_spyder >=0.4.0           :  0.4.0 (OK)
pylsp >=1.3.2;<1.4.0          :  1.3.3 (OK)
pylsp_black >=1.0.0           :  1.0.0 (OK)
qdarkstyle =3.0.2             :  3.0.2 (OK)
qstylizer >=0.1.10            :  0.1.10 (OK)
qtawesome >=1.0.2             :  1.0.3 (OK)
qtconsole >=5.2.1;<5.3.0      :  5.2.2 (OK)
qtpy >=1.5.0                  :  2.2.0 (OK)
rtree >=0.9.7                 :  1.0.0 (OK)
setuptools >=49.6.0           :  63.4.1 (OK)
sphinx >=0.6.6                :  5.0.2 (OK)
spyder_kernels >=2.2.1;<2.3.0 :  2.2.1 (OK)
textdistance >=4.2.0          :  4.2.1 (OK)
three_merge >=0.1.1           :  0.1.1 (OK)
watchdog >=0.10.3             :  2.1.6 (OK)
zmq >=17                      :  23.2.0 (OK)

# Optional:
cython >=0.21                 :  0.29.30 (OK)
matplotlib >=2.0.0            :  3.5.2 (OK)
numpy >=1.7                   :  1.21.5 (OK)
pandas >=1.1.1                :  1.4.3 (OK)
scipy >=0.17.0                :  1.7.3 (OK)
sympy >=0.7.3                 :  1.10.1 (OK)
```



Discussion:
Hi @XueqiC thank you for the feedback! I'm not sure what could be happening here. Seems like for some reason when the kernel is generating the info for the variables available is triggering an error. Maybe checking the option on the Variable Explorer to `Exclude unsupported data types` or `Exclude callables and modules`?

I tried reproducing this with Spyder 5.3.3 but seems like the code runs fine with that version:

![imagen](https://user-images.githubusercontent.com/16781833/195413404-d644afac-aed4-42f9-8fb3-28c3110da625.png)

Maybe updating Spyder could help? (however since seems like you are using your base environment probably you will need to first remove Spyder and then install it specifing `spyder=5.3.3`)

Also, just in case, maybe you know what could be hapenning here or have suggestions @ccordoba12 @impact27 ?
I really don't understand what's happening in this case. It seems that Numpy is trying to print a warning, which fails because our `WriteWrapper` class (in charge of standard streams when calling handlers) was not properly initialized. I say it because it seems we end up creating that class without a `_thread_id` attribute, which should be set in its `__init__` method.

A possible solution for this would be to avoid showing messages for the `get_namespace_view` handler because it generates too much noise for very little gain. Another idea is to check that `WriteWrapper` has the `_thread_id` attribute in line

```python
if self._thread_id != threading.get_ident():
```

which is kind of odd, but it'd do the job too.

@impact27, other ideas about this?
> A possible solution for this would be to avoid showing messages for the `get_namespace_view` handler because it generates too much noise for very little gain.

The problem is that if we don't do that, random text might appear in the console with no clue for the user as to where it comes from.
This is strange. Looking at the traceback:
```
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/comms/frontendcomm.py", line 310, in __call__
    self._write(
  File "/Users/chengxueqi/opt/anaconda3/lib/python3.9/site-packages/spyder_kernels/comms/frontendcomm.py", line 300, in __call__
    if self._thread_id != threading.get_ident():
```
It looks like the `WriteWrapper` wraps around another `WriteWrapper`, which is not good :/ It probably means that two comm messages are received on different threads at the same time (shell + control) and that this creates the bug somehow. I am unsure why the second `WriteWrapper` is uninitialised however.
If you can find a way to reproduce this error, maybe https://github.com/spyder-ide/spyder-kernels/pull/420 can help.
Thanks @impact27! I really don't know how to reproduce this but let's go with your solution and wait to see if another user reports the same problem.

Resolving patch:
diff --git a/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py b/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py
--- a/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py
+++ b/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py
@@ -64,7 +64,7 @@ def __init__(self, kernel):
         self.register_call_handler('_send_comm_config',
                                    self._send_comm_config)
 
-        self.comm_lock = threading.Lock()
+        self.comm_lock = threading.RLock()
 
         # self.kernel.parent is IPKernelApp unless we are in tests
         if self.kernel.parent:
@@ -259,19 +259,22 @@ def handle_msg(msg):
 
     def _remote_callback(self, call_name, call_args, call_kwargs):
         """Call the callback function for the remote call."""
-        saved_stdout_write = sys.stdout.write
-        saved_stderr_write = sys.stderr.write
-        thread_id = threading.get_ident()
-        sys.stdout.write = WriteWrapper(
-            saved_stdout_write, call_name, thread_id)
-        sys.stderr.write = WriteWrapper(
-            saved_stderr_write, call_name, thread_id)
-        try:
-            return super(FrontendComm, self)._remote_callback(
-                call_name, call_args, call_kwargs)
-        finally:
-            sys.stdout.write = saved_stdout_write
-            sys.stderr.write = saved_stderr_write
+        with self.comm_lock:
+            current_stdout = sys.stdout
+            current_stderr = sys.stderr
+            saved_stdout_write = current_stdout.write
+            saved_stderr_write = current_stderr.write
+            thread_id = threading.get_ident()
+            current_stdout.write = WriteWrapper(
+                saved_stdout_write, call_name, thread_id)
+            current_stderr.write = WriteWrapper(
+                saved_stderr_write, call_name, thread_id)
+            try:
+                return super(FrontendComm, self)._remote_callback(
+                    call_name, call_args, call_kwargs)
+            finally:
+                current_stdout.write = saved_stdout_write
+                current_stderr.write = saved_stderr_write
 
 
 class WriteWrapper(object):


Test patch: