1iveowl / WebsocketClientLite.PCL

Websocket client lite
MIT License
39 stars 14 forks source link

Disconnecting from server causes exception #26

Closed brooksyott closed 7 years ago

brooksyott commented 7 years ago

Hello,

So far, I'm liking this library a lot. A major issue I have though at the moment is when a connection to the internet is lost, or the far end drops the connection, I get an exception when I use SendTextAsync(String). It appears I can't catch the exception (or so far I haven't been able to). Any ideas how to work around this issue? To reproduce this, I simply disable my internet connection while sending messages

The exception I get is:

System.IO.IOException: Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. ---> System.Net.Sockets.SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult) at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult) --- End of inner exception stack trace --- at System.Net.Security._SslStream.EndRead(IAsyncResult asyncResult) at System.Net.Security.SslStream.EndRead(IAsyncResult asyncResult) at System.IO.Stream.<>c.b43_1(Stream stream, IAsyncResult asyncResult) at System.Threading.Tasks.TaskFactory1.FromAsyncTrimPromise1.Complete(TInstance thisRef, Func3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at WebsocketClientLite.PCL.Service.WebsocketListener.<ReadOneByteAtTheTimeAsync>d__26.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Reactive.PlatformServices.ExceptionServicesImpl.Rethrow(Exception exception) at System.Reactive.Stubs.<>c.<.cctor>b__2_1(Exception ex) at System.Reactive.AnonymousSafeObserver1.OnError(Exception error) at System.Reactive.Linq.ObservableImpl.AsObservable1._.OnError(Exception error) at System.Reactive.Subjects.Subject1.OnError(Exception error) at WebsocketClientLite.PCL.Service.WebsocketListener.b__271(Exception ex) at System.Reactive.AnonymousSafeObserver1.OnError(Exception error) at System.Reactive.Linq.ObservableImpl.Where1..OnError(Exception error) at System.Reactive.Linq.ObservableImpl.Select2._.OnError(Exception error) at System.Reactive.ScheduledObserver1.Dispatch(ICancelable cancel) at System.Reactive.Concurrency.Scheduler.<>c.b72_0(Action1 a, ICancelable c) at System.Reactive.Concurrency.DefaultScheduler.LongRunning.<>c__DisplayClass1_01.b0(Object arg) at System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.<>cDisplayClass7_0.b__0() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()

1iveowl commented 7 years ago

Thank you for raising this. The documentation is not clear on this and I will update it.

What you need to do is to listen for the exception in your subscription. Like this:

        _subscribeToMessagesReceived = websocketClient.ObserveTextMessagesReceived.Subscribe(
            msg =>
            {
                System.Console.WriteLine($"Reply from test server: {msg}");
            },
            ex =>
            {
                System.Console.WriteLine(ex.Message);
            },
            () =>
            {
                System.Console.WriteLine($"Subscription Completed");
            });

In the above the subscription listens to three things:

  1. The messages
  2. Exceptions
  3. Completion

This is the pattern used with Rx.

Please let me know if this helps?

brooksyott commented 7 years ago

It was very helpful, thank you. The exception does come now, terrific. If I leave the network disconnected what I see is:

It seems to work, but it's not pretty :).

Again, thank you so much for the quick reply today. It definitely helped

1iveowl commented 7 years ago

Great to hear.

What you describe makes perfect sense.

When the connection fails you need to re-connect it before you try sending something new. Not sure you need to create a new instance of the MessageWebSocketRx object.

From the moment the connection fails to you receive an exception via Rx there is a time gap. In mine testing it was 20 seconds. This makes sense as the client don't know that the connection is gone. It has to timeout first.

1iveowl commented 7 years ago

Regarding looking pretty. I would it something like this:

    static void Main(string[] args)
    {

        var outerCancellationSource = new CancellationTokenSource();

        Task.Run(() => StartWebSocketAsyncWithRetry(outerCancellationSource), outerCancellationSource.Token);

        System.Console.WriteLine("Waiting...");
        System.Console.ReadKey();
        outerCancellationSource.Cancel();

        _subscribeToMessagesReceived.Dispose();
    }

    private static async Task StartWebSocketAsyncWithRetry(CancellationTokenSource outerCancellationTokenSource)
    {
        while (!outerCancellationTokenSource.IsCancellationRequested)
        {
            var innerCancellationSource = new CancellationTokenSource();
            await StartWebSocketAsync(innerCancellationSource);

            while (!innerCancellationSource.IsCancellationRequested)
            {
                await Task.Delay(TimeSpan.FromSeconds(10), innerCancellationSource.Token);
            }

            // Wait 5 seconds before trying again
            await Task.Delay(TimeSpan.FromSeconds(5), outerCancellationTokenSource.Token);
        }
    }

    private static async Task StartWebSocketAsync(CancellationTokenSource innerCancellationTokenSource)
    {
        using (var websocketClient = new MessageWebSocketRx())
        {
            _subscribeToMessagesReceived = websocketClient.ObserveTextMessagesReceived.Subscribe(
                msg =>
                {
                    // Msg receive code goes here
                },
                ex =>
                {
                    // Cancel the inner loop when exception
                    innerCancellationTokenSource.Cancel();
                },
                () =>
                {
                    // Cancel the inner loop when complete
                    innerCancellationTokenSource.Cancel();
                });
....

See example here