Repository: EvoluxBR/greenswitch

Issue:
ESLProtocol.connect: Socket is not cleaned up if socket is closed
This issue is very similar to #82 

When `ESLProtocol.connect` is called, it issues a `socket.send`, but, in rare cases, it could raise an exception if socket is already closed, but the socket is not cleanup

```ruby
mai 22 16:18:14: Traceback (most recent call last):
mai 22 16:18:14:   File "src/gevent/greenlet.py", line 906, in gevent._gevent_cgreenlet.Greenlet.run
mai 22 16:18:14:   File "<retracted>/server.py", line 36, in _accept_call
mai 22 16:18:14:     super()._accept_call(session)
mai 22 16:18:14:   File "<retracted>/python/greenswitch/esl.py", line 593, in _accept_call
mai 22 16:18:14:     self._handle_call(session)
mai 22 16:18:14:   File "<retracted>/server.py", line 59, in _handle_call
mai 22 16:18:14:     super()._handle_call(session)
mai 22 16:18:14:   File "<retracted>/python/greenswitch/esl.py", line 596, in _handle_call
mai 22 16:18:14:     session.connect()
mai 22 16:18:14:   File "<retracted>/python/greenswitch/esl.py", line 362, in connect
mai 22 16:18:14:     resp = self.send('connect')
mai 22 16:18:14:   File "<retracted>/python/greenswitch/esl.py", line 213, in send
mai 22 16:18:14:     response = async_response.get()
mai 22 16:18:14:   File "src/gevent/event.py", line 329, in gevent._gevent_cevent.AsyncResult.get
mai 22 16:18:14:   File "src/gevent/event.py", line 359, in gevent._gevent_cevent.AsyncResult.get
mai 22 16:18:14:   File "src/gevent/event.py", line 347, in gevent._gevent_cevent.AsyncResult.get
mai 22 16:18:14:   File "src/gevent/event.py", line 327, in gevent._gevent_cevent.AsyncResult._raise_exception
mai 22 16:18:14:   File "<retracted>/python/gevent/_compat.py", line 66, in reraise
mai 22 16:18:14:     raise value
mai 22 16:18:14: greenswitch.esl.OutboundSessionHasGoneAway
```
The socket will stay in `CLOSE_WAIT` leaking a file descriptor forever


Discussion:

Patch:
diff --git a/greenswitch/esl.py b/greenswitch/esl.py
index 6c25f29..9f9bdaf 100644
--- a/greenswitch/esl.py
+++ b/greenswitch/esl.py
@@ -359,7 +359,12 @@ class OutboundSession(ESLProtocol):
         if self._outbound_connected:
             return self.session_data
 
-        resp = self.send('connect')
+        try:
+            resp = self.send('connect')
+        except OutboundSessionHasGoneAway as e:
+            # cleanup before raising exception
+            self.stop()
+            raise e
         self.session_data = resp.headers
         self._outbound_connected = True
 


Test patch:
diff --git a/tests/test_outbound_session.py b/tests/test_outbound_session.py
index 69205b3..dc5a863 100644
--- a/tests/test_outbound_session.py
+++ b/tests/test_outbound_session.py
@@ -25,6 +25,23 @@ class TestOutboundSession(unittest.TestCase):
         with self.assertRaises(esl.OutboundSessionHasGoneAway):
             self.outbound_session.raise_if_disconnected()
 
+    def test_raising_OutboundSessionHasGoneAway_in_session_connect_will_call_sock_close(self):
+        # Here we are socket connected, but not freeswitch connected
+        # So any exception raised in session.connect should call sock.close
+        self.outbound_session.connected = False
+
+        # make outbound_session.send raise OutboundSessionHasGoneAway
+        self.outbound_session.send = mock.MagicMock(side_effect=esl.OutboundSessionHasGoneAway)
+
+        # mock sock.close
+        self.outbound_session.sock.close = mock.MagicMock()
+
+        # attempt to connect and assert sock.close is called
+        with self.assertRaises(esl.OutboundSessionHasGoneAway):
+            self.outbound_session.connect()
+
+        assert self.outbound_session.sock.close.called
+
 
 @pytest.mark.usefixtures("outbound_session")
 @pytest.mark.usefixtures("disconnect_event")