Developing on Mac OS X, Waitress, PDBDebugmode and [Errno 41] Protocol wrong type for socket

When you develop on OS X, and start Plone 5.2 in foreground with PDBDebugMode activated, you ocassionally can see the following error:

2021-02-11 22:58:18,907 ERROR   [waitress:121][MainThread] Socket error
Traceback (most recent call last):
  File "/Users/fred/.buildout/eggs/waitress-1.4.4-py3.8.egg/waitress/channel.py", line 118, in handle_write
    flush()
  File "/Users/fred/.buildout/eggs/waitress-1.4.4-py3.8.egg/waitress/channel.py", line 227, in _flush_some
    num_sent = self.send(chunk)
  File "/Users/fred/.buildout/eggs/waitress-1.4.4-py3.8.egg/waitress/wasyncore.py", line 433, in send
    result = self.socket.send(data)
OSError: [Errno 41] Protocol wrong type for socket
[Errno 41] Protocol wrong type for socket

Which is very annoying because it seems to be random and it throws you into a pdb where you have to continue first to get started. It's easy to ignore, but still mosquito iritating, you keep waving your hands :-(. I did some searching yesterday and it's reported by other communities as well, not limited to Python, but specific for OS X

The most thorough explanation IMHO was written by a Rust developer in 2014:
http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/

If I understand his blog post correctly its an edge case where OS X return a wrong code when a socket is being closed but some data is still sent. His solution in Rust at the end of the article is something I think we can apply 1:1 to Python/Waitress.

It's this method in waitress wasyncore.py:

If you import EPROTOTYPE as well from errno (imports at the top of wasyncore.py) and add the two commented lines below, you just ignore it.

Maybe instead of pass, return 0 would be better. After I added this locally, the random Pdb's so far have stopped.

    def send(self, data):
        try:
            result = self.socket.send(data)
            return result
        except socket.error as why:
            if why.args[0] == EWOULDBLOCK:
                return 0
            elif why.args[0] in _DISCONNECTED:
                self.handle_close()
                return 0
            #elif why.args[0] == EPROTOTYPE:
            #    pass
            else:
                raise
2 Likes

Thanks, I've added your patch and have not been pestered by this issue since!
Any chance of getting this upstream as a pull-request into waitress?