Requests, YCM, and FreeBSD error

Using the [YouCompleteMe (YCM)](https://github.com/Valloric/YouCompleteMe) project with vim I ran into a [problem](https://github.com/Valloric/YouCompleteMe/issues/804) that seems to come from Requests and running on FreeBSD 9.2-STABLE. The traceback I am running into:

``` python
Exception in thread Thread-10 (most likely raised during interpreter shutdown):
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/threading.py", line 810, in __bootstrap_inner
  File "/usr/local/lib/python2.7/threading.py", line 763, in run
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/autoload/../python/ycm/unsafe_thread_pool_executor.py", line 55, in _worker
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/autoload/../python/ycm/unsafe_thread_pool_executor.py", line 43, in run
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/third_party/requests/requests/sessions.py", line 357, in request
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/third_party/requests/requests/sessions.py", line 460, in send
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/third_party/requests/requests/adapters.py", line 350, in send
<type 'exceptions.AttributeError'>: 'NoneType' object has no attribute 'error'
```

Requests is embedded within YCM as a Subproject, currently @ 3373548.

What kind of additional information do I need to provide to be able to track down what goes wrong here?


Is that really the whole traceback? I'm finding it bears no relationship to the actual code in Requests.

Nevertheless, assuming it does, the only place we could raise an uncaught `AttributeError` of this type would have to be in the except block where we attempt to get `socket.error`. Surely `socket` provides `socket.error` on FreeBSD 9.2?


Yes it is the whole traceback. Here is a slightly more up to date one with latest YCM so that line numbers match better:

``` python
Exception in thread Thread-2 (most likely raised during interpreter shutdown):
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/threading.py", line 810, in __bootstrap_inner
  File "/usr/local/lib/python2.7/threading.py", line 763, in run
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/autoload/../python/ycm/unsafe_thread_pool_executor.py", line 55, in _worker
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/autoload/../python/ycm/unsafe_thread_pool_executor.py", line 43, in run
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/third_party/requests/requests/sessions.py", line 383, in request
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/third_party/requests/requests/sessions.py", line 486, in send
  File "/usr/home/asmodai/.vim/bundle/YouCompleteMe/third_party/requests/requests/adapters.py", line 374, in send
<type 'exceptions.AttributeError'>: 'NoneType' object has no attribute 'error'      
```

On my FreeBSD 9.2-STABLE box:

``` python
>>> from socket import error
>>> socket.error
<class 'socket.error'>
```

So yes, it provides it. But isn't this about the fact that socket isn't defined at this point? The traceback says that object NoneType has no attribute error, which makes sense because a None has no attributes. But that would mean, if I look at code in adapters.py that somehow the import of socket did not occur or got clobbered somehow?

``` python
374:        except socket.error as sockerr:
```

I do not know the Requests codebase well enough to be able to judge whether this can happen.

I am a bit caught between YCM and Requests at the moment in trying to see where it goes wrong exactly. I also double checked to see whether Python called from within Python actually can import socket and that works without problems.


Can you test using YCM's "unsafe thread pool"? That is referenced in the traceback and sounds highly suspicious. 


Mm, I agree with @sigmavirus24 here. Something weird should be going on with YCM to cause this. Here's my reasoning:
1. `socket` must be imported because it's unconditionally imported at the top of `adapters.py`: any failure to import would cause an `ImportError`.
2. `socket` must be imported because if the import hadn't run at all we wouldn't get an `AttributeError`, we'd get a `NameError` on `socket`.

These two facts suggest that `requests.adapters.socket` has been monkeypatched to `None`. We never do this in Requests, so I'd look at YCM.


Yeah, makes sense to me.

Especially when I see this in the `unsafe_thread_pool_executor.py`:

``` python
# This file provides an UnsafeThreadPoolExecutor, which operates exactly like
# the upstream Python version of ThreadPoolExecutor with one exception: it
# doesn't wait for worker threads to finish before shutting down the Python
# interpreter.
#
# This is dangerous for many workloads, but fine for some (like when threads
# only send network requests). The YCM workload is one of those workloads where
# it's safe (the aforementioned network requests case).
```

I haven't had time yet to work with the "unsafe thread pool", but I am suspecting it will indeed point back to YCM. Yay, issue ping-pong. ;)


Further if they're monkey patching socket with something like gevent, then that could cause `socket` to be `None`.


Looking into YCM, they use pythonfutures and [requests-futures](https://github.com/ross/requests-futures).  I wonder if @ross has any insight into this. Granted I'm not sure they actually use requests-futures, but they're at least using requests with concurrent futures and he might be able to help us all out.


base_request.py seems to import requests-futures and creates a FuturesSession. the code is a bit difficult to follow, but what it is doing with requests-futures _seems_ valid and i don't see any clear way it'd be related to the socket.error problem. 

the fact that unsafe_thread_pool_executor.py has comments about it being safe to kill still working threads and the stacktrace mentions that it's probably happening in shutdown would in my opinion point things at YCM.


Fair enough, I'll take it up with @Valloric again. :)


Thanks @ross !


Yes, thanks for the help so far guys, much appreciated. I'll close it for now.


> Looking into YCM, they use pythonfutures and requests-futures. I wonder if @ross has any insight into this. Granted I'm not sure they actually use requests-futures, but they're at least using requests with concurrent futures and he might be able to help us all out.

YCM uses requests-futures, correct.

> base_request.py seems to import requests-futures and creates a FuturesSession. the code is a bit difficult to follow, but what it is doing with requests-futures seems valid and i don't see any clear way it'd be related to the socket.error problem.

YCM code doesn't do any monkeypatching of anything inside Requests (or any other third-party lib it uses). requests-futures might be doing it though, I'm not sure.

> the fact that unsafe_thread_pool_executor.py has comments about it being safe to kill still working threads and the stacktrace mentions that it's probably happening in shutdown would in my opinion point things at YCM.

Yeah, the `unsafe_thread_pool_executor` is unsafe in general (that's why I named it that way) but safe for YCM workloads. When the user wants to shut down Vim, the YCM plugin that uses Requests to talk to the `ycmd` server might have some requests in flight and we don't care about those anymore, they're (logically) fine to kill at any point of execution. But from a code perspective, it appears it might provoke some tracebacks... _sigh_.

But waiting on the network threads to stop is profoundly not an option; depending on the user's code (that they're trying to get completions for), some of those requests might take several seconds to finish and blocking Vim shutdown to wait for them pisses everyone off. People want Vim to exit quickly, not block and then exit some time in the near future.

I'm open to ideas here.


I think I'll just make YCM's `unsafe_thread_pool_executor` swallow this exception during shutdown. It's a shitty solution, but others are even more so.


@Valloric Sorry I couldn't get back to you after your first message, but yeah, if you genuinely don't care about what happens to the messages once you've decided to shut down you should just swallow those exceptions. After all, at this point they aren't exceptional, they're expected.


Are we certain that this is occurring when @ashemedai is quitting vim? I didn't see him indicate that but perhaps I missed it in the original thread.


@sigmavirus24 It happens for me only when I write-quit or quit out of vim and only from time to time. I am fine with it silently swallowing it. The reason I brought it up in the first place with @Valloric was that I thought it was unintended behaviour. :)


In that case, ignore me. :)