Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid NullReferenceException in TcpClient.EndConnect #899

Merged
merged 1 commit into from
Jul 8, 2024

Conversation

Paciv
Copy link
Contributor

@Paciv Paciv commented Jun 11, 2024

When the timeout is shorter than the time TcpClient.ConnectAsync takes to throw
The task.Wait returns before the end of the task, then dispose the Tcpclient before the end of its ConnectAsync call

When the timeout is shorter than the time TcpClient.ConnectAsync takes to throw
The task.Wait returns before the end of the task, then dispose the Tcpclient before the end of its ConnectAsync call
@scottf
Copy link
Collaborator

scottf commented Jun 12, 2024

Could you possibly make a unit test to demonstrate this?

@Paciv
Copy link
Contributor Author

Paciv commented Jun 12, 2024

It actually only throws a null reference exception internally on the task that is not awaited anymore, because the TcpClient have been disposed before the end of its ConnectAsync method.

I did a small repro case, but I don't see how that could fit in a unit test

        static void Main(string[] args)
        {
            TcpClient tcpClient = new();            
            var task = tcpClient.ConnectAsync("unreachable.destination.com", 4444);

            Exception nce = null;
            try
            {
                // force too short timeout
                if (!task.Wait(TimeSpan.FromMilliseconds(1)))
                {
                    nce = new Exception("timeout");
                }
            }
            catch (AggregateException ex)
            {
                nce = new Exception(ex.InnerException.Message);
            }

            if (nce != null)
            {
                tcpClient.Dispose();
                
                // Get the TcpClient NullReference exception (should get a UnknownHost instead)
                task.Wait();

                tcpClient = null;
                throw nce;
            }
        }

@@ -394,16 +394,17 @@ public virtual void open(Srv s, Options options)
if (!task.Wait(TimeSpan.FromMilliseconds(options.Timeout)))
{
nce = new NATSConnectionException("timeout");
task.ContinueWith(t => close(client));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I understand. The task is still running even though we aren't waiting for it. Since we timed-out we don't want the connection, so close it.

}
}
catch (Exception e)
{
nce = new NATSConnectionException(e.Message);
close(client);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it possible that the same thing happens? Maybe a better question is when would an exception be thrown.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an exception is thrown here, it means that the task is completed, and that it ended in an exception.
That's why we can safely dispose the TcpClient

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if the task of making the tcp client failed, is there anything to safely close?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It disposes the TcpClient and its underlying Socket in a timely manner, after the connection failed.
If not disposed here, it is bound to be disposed at some point by the GC because the reference is not rooted, but it is always better to avoid unpredictable behaviors.

@scottf
Copy link
Collaborator

scottf commented Jun 13, 2024

Can we wrap it in an exception handler just so it doesn't fail. It's probably overkill but doesn't really hurt anything. Other than that this is ready to merge.

@Paciv
Copy link
Contributor Author

Paciv commented Jun 19, 2024

It looks overkill, yes.
Moreover, I wouldn't know what to do with the new exception at this point. There is already one to throw, so the new one would be logged at best or hidden, which isn't really good design.
I believe that if an internal exception is thrown by the .NET Framework Dispose on the TcpClient, we may want to rethrow it anyway, because it would mean something very bad happened.

Also this hasn't been sufficiently tested to be released as is, there would be the need to test connectivity on a mix of multiple reachable and non-reachable node list.

@scottf scottf merged commit e5aaea9 into nats-io:main Jul 8, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants