SSL3_WRITE_PENDING error from urllib3.contrib.pyopenssl#sendall
See original GitHub issueI’m writing a program that makes somewhat large (e.g. tens to hundreds of kilobytes) POST requests over HTTPS using requests. Such POSTs fail due to SSL3_WRITE_PENDING errors from OpenSSL. In short, SSL3_WRITE_PENDING occurs when an incomplete write operation (i.e. a write that resulted in a WantWriteError) is retried with arguments not exactly equal to those passed in the initial call (see here and here).
Stracktrace from my call into requests down through urllib3:
File "/usr/local/lib/python2.7/site-packages/requests/sessions.py", line 511, in post
return self.request('POST', url, data=data, json=json, **kwargs)
File "/usr/local/lib/python2.7/site-packages/requests/sessions.py", line 468, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/lib/python2.7/site-packages/requests/sessions.py", line 576, in send
r = adapter.send(request, **kwargs)
File "/usr/local/lib/python2.7/site-packages/requests/adapters.py", line 370, in send
timeout=timeout
File "/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 559, in urlopen
body=body, headers=headers)
File "/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 353, in _make_request
conn.request(method, url, **httplib_request_kw)
File "/usr/local/lib/python2.7/httplib.py", line 1053, in request
self._send_request(method, url, body, headers)
File "/usr/local/lib/python2.7/httplib.py", line 1093, in _send_request
self.endheaders(body)
File "/usr/local/lib/python2.7/httplib.py", line 1049, in endheaders
self._send_output(message_body)
File "/usr/local/lib/python2.7/httplib.py", line 893, in _send_output
self.send(msg)
File "/usr/local/lib/python2.7/httplib.py", line 869, in send
self.sock.sendall(data)
File "/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 220, in sendall
sent = self._send_until_done(data[total_sent:total_sent+SSL_WRITE_BLOCKSIZE])
File "/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/contrib/pyopenssl.py", line 206, in _send_until_done
return self.connection.send(data)
File "/usr/local/lib/python2.7/site-packages/OpenSSL/SSL.py", line 1271, in send
self._raise_ssl_error(self._ssl, result)
File "/usr/local/lib/python2.7/site-packages/OpenSSL/SSL.py", line 1187, in _raise_ssl_error
_raise_current_error()
File "/usr/local/lib/python2.7/site-packages/OpenSSL/_util.py", line 48, in exception_from_error_queue
raise exception_type(errors)
Error: [('SSL routines', 'SSL3_WRITE_PENDING', 'bad write retry')]
A bit of digging revealed #412 and #626 as issues that relate to the relevant code in urllib3. WrappedSocket.sendall proxies to WrappedSocket._send_until_done, which invokes OpenSSL.SSL.send in a loop. The conditions for loop termination are a) the write succeeds or b) a timeout expires while waiting for the socket to become writable in the case of a WantWriteError.
I think that this issue boils down to:
WrappedSocket.sendallconverts the data to be sent to amemoryview.WrappedSocket._send_until_doneinvokesOpenSSL.SSL.sendin a loop according to the conditions above.OpenSSL.SSL.sendcalls thememoryview.tobytes()and passes the bytestring result in the OpenSSL write call.- In the
WantWriteErrorcase, 2 and 3 are repeated and a new bytestring is passed to the OpenSSL write, resulting in aSSL3_WRITE_PENDINGerror.
I was able to get around this issue by patching urllib3/contrib/pyopenssl.py to enforce that a single bytestring is used for all calls to OpenSSL.SSL.send: https://gist.github.com/evnm/af92092c6a7776e08339
Please advise on whether this is a good way to fix the issue. I tried adding a test case to test/with_dummyserver/test_https.py, but haven’t figured out how to tickle the specific issue I’ve run into.
Relevant versions in use:
- Python 2.7.10
- requests 2.8.1
- pyOpenSSL 0.15.1
- urllib3 1.12
Issue Analytics
- State:
- Created 8 years ago
- Comments:24 (12 by maintainers)

Top Related StackOverflow Question
I’ll see if I can isolate it, and provide a simple repro.
Things we (think we) know so far:
requestshere indirectly, to connect to ElasticSearch via theelasticsearchPython library, withconnection_class=elasticsearch.connection.RequestsHttpConnectionelasticsearch.helpers.bulkbulk()with the defaults, which are up to 500 documents at once (we probably hit this sometimes) and 100MB (we never hit this)requestsfrom 2.8.1 to 2.9.1 (we also used 2.3.0 for a while before upgrading to 2.8.1), but a few other things changed around the same timeThe issue seems to come from requests_aws4auth:
https://github.com/sam-washington/requests-aws4auth/issues/24