Repository: 1c3t3a/rust-socketio (language: rust)

Issue:
Performance degradation using SSL
When connecting with HTTPS to a Flask SocketIO server I'm seeing a slowdown of 20 seconds waiting for a response compared to http.

This is happening with header auth and with certificate auth.

```
let socket = SocketBuilder::new("https://$IP$".to_string())
        .opening_header(
            "Authorization",
            settings.header_auth_value.as_str()
        )
        .on("open", connect_callback)
        .on("submit", submit_callback)
        .on("error", |err, _| {
            warn!("Server Connection Error: {:#?}", err)
        })
        .on("error", |err, _| eprintln!("Error: {:#?}", err))
        // .transport_type(TransportType::WebsocketUpgrade)
        //  .tls_config(tls_connector)
        .connect();
```

I can see in the nginx logs that the websocket upgrade is switching protocols

`client  GET /socket.io/?EIO=4&sid=n4wlLsEmzjqGaiGHAAAG&transport=websocket HTTP/1.1 101 45 `

Any ideas on what could be going wrong? 

PS. Not seeing this degradation with python socket io client 


Discussion / hints:
Mhh... 20 seconds definitely sounds weird... Generally a small degradation can be expected, mostly as the underlying websocket library does not allow a `split` of the stream into `receiver` and `sender` (see [here](https://docs.rs/websocket/0.26.2/websocket/client/builder/struct.ClientBuilder.html#method.connect_insecure) vs [here](https://docs.rs/websocket/0.26.2/websocket/client/builder/struct.ClientBuilder.html#method.connect_secure)). This would be fixed when this crate gets an async implementation, that could use the `tungstenite` websocket library, see #115. 

Anyways a degradation of up to 20 seconds is strange, in which way is the server configured? Do you see an immediate answer to the upgrade request?
Initial connection is very quick with the websocket upgrade. I've also pulled the branch with the tungstenite websocket library and seeing the same slow down waiting for a message after the server emits. 

Server is flask socket io running on Gunicorn (with gevent websocket worker) using nginx to handle ssl and reverse proxy. I also see the slowdown using the debug flask server and not gunicorn. I don't see the slowdown with python socket io client though so I believe the server to be configured correctly.

I'll test deploying your node.js benchmark server remotely and see if I see a slowdown 
Running the tests remotely using the secure node server I don't seem to see any delay. I bypassed nginx and used the same certificates on the flask-socketio server and do see it though, no certificate errors etc and forcing websockets not polling 
I did some more debugging and added some timers and found the 25 second wait at acquiring the lock on the MutexGuard<Client<TlsStream<TcpStream>>>> in  [WebsocketSecureTransport](https://github.com/1c3t3a/rust-socketio/blob/c49fe9e0d6d45a6a803a527c29d949d4874cf3c0/engineio/src/transports/websocket_secure.rs#L91)

        let before = Instant::now();
        let mut writer = self.client.lock()?;
        println!("Waiting acquire mutexguard LOCK for: {:.2?}", before.elapsed());

Once the lock is acquired it sends the message quickly. Any ideas on why the lock would be held for so long? 
Ah great, thanks for the detailed information! I think I tracked down the issue, and 20 seconds makes total sense in that context. Without splitting, the same connection for sending and receiving is used, but receiving is a blocking operation: https://github.com/1c3t3a/rust-socketio/blob/c49fe9e0d6d45a6a803a527c29d949d4874cf3c0/engineio/src/transports/websocket_secure.rs#L99-L104

After the connection is established, the client starts to wait for a message from the server by calling the blocking receive message. At the same time, in another thread (the main one by "managed" by the user) the send method is called and starts to wait for the lock. So now we're in a deadlock situation, that only gets resolved after *20* seconds, the servers ping interval! Then the lock gets dropped and the client can send. 

If I am right the interval should get shorter with a shorter ping interval in the server configuration. Does this make sense @nshaaban-cPacket?

Sorry for the inconvenience, this looks like a logical error!

A possible solution would be to introduce a timeout on the receiver, so that the lock gets freed regularly.

Fix patch:
diff --git a/engineio/src/transports/websocket_secure.rs b/engineio/src/transports/websocket_secure.rs
index c89c959b..ee5c5961 100644
--- a/engineio/src/transports/websocket_secure.rs
+++ b/engineio/src/transports/websocket_secure.rs
@@ -7,15 +7,19 @@ use bytes::{BufMut, Bytes, BytesMut};
 use native_tls::TlsConnector;
 use std::borrow::Cow;
 use std::convert::TryFrom;
+use std::io::ErrorKind;
 use std::str::from_utf8;
 use std::sync::{Arc, Mutex, RwLock};
+use std::time::Duration;
+use websocket::WebSocketError;
 use websocket::{
     client::sync::Client as WsClient,
     client::Url,
+    dataframe::DataFrame,
     dataframe::Opcode,
     header::Headers,
     sync::stream::{TcpStream, TlsStream},
-    ws::dataframe::DataFrame,
+    ws::dataframe::DataFrame as DataFrameable,
     ClientBuilder as WsClientBuilder, Message,
 };
 
@@ -40,6 +44,11 @@ impl WebsocketSecureTransport {
             client_builder = client_builder.custom_headers(&headers);
         }
         let client = client_builder.connect_secure(tls_config)?;
+        // set a read timeout on the client to occasionally release the client lock during read.
+        client
+            .stream_ref()
+            .get_ref()
+            .set_read_timeout(Some(Duration::from_millis(200)))?;
 
         client.set_nonblocking(false)?;
 
@@ -96,11 +105,29 @@ impl Transport for WebsocketSecureTransport {
     }
 
     fn poll(&self) -> Result<Bytes> {
-        let mut receiver = self.client.lock()?;
+        let received_df: DataFrame;
+        loop {
+            let mut receiver = self.client.lock()?;
+            match receiver.recv_dataframe() {
+                Ok(payload) => {
+                    received_df = payload;
+                    break;
+                }
+                // Special case to fix https://github.com/1c3t3a/rust-socketio/issues/133
+                // This error occures when the websocket connection times out on the receive method.
+                // The timeout is defined on the underlying TcpStream (see `WebsocketSecureTransport::new`).
+                // The error kind is platform specific, on Unix systems this errors with `ErrorKind::WouldBlock`,
+                // on Windows with `ErrorKind::TimedOut`.
+                // As a result we're going to release the lock on the client,
+                // so that other threads (especially emit) are able to access the client.
+                Err(WebSocketError::IoError(err))
+                    if matches!(err.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => {}
+                Err(err) => return Err(err.into()),
+            }
+            drop(receiver);
+        }
 
         // if this is a binary payload, we mark it as a message
-        let received_df = receiver.recv_dataframe()?;
-        drop(receiver);
         match received_df.opcode {
             Opcode::Binary => {
                 let mut message = BytesMut::with_capacity(received_df.data.len() + 1);
@@ -155,6 +182,7 @@ mod test {
             None,
         )
     }
+
     #[test]
     fn websocket_secure_transport_base_url() -> Result<()> {
         let transport = new()?;


Test patch: