Skip to content

try all addresses returned by getAddrInfo #133

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

Merged
merged 1 commit into from
Mar 20, 2021

Conversation

frasertweedale
Copy link
Contributor

Currently, openTCPConnection only considered the first address
returned by getAddrInfo. This can cause connection failures. For
example, if the first address returned is an IPv6 address, but the
HTTP server only listens on IPv4, then the connection will be
refused, and the library gives up.

Update openTCPConnection to try each address returned by
getAddrInfo. This is a naïve algorithm; better approaches are
possible (e.g. see https://en.wikipedia.org/wiki/Happy_Eyeballs).

This change may regress performance of this library if getAddrInfo
returns two or more addresses and several connection attempts time
out (as opposed to "connection refused"). But most users would
probably prefer to succeed slowly than to fail unnecessarily.

Copy link
Member

@hsenag hsenag left a comment

Choose a reason for hiding this comment

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

Looks good - is it possible/worthwhile to write a test? (I don't feel strongly either way)

Comment on lines +255 to +260
-- single AddrInfo; call connectAddrInfo directly so that specific
-- exception is thrown in event of failure
Copy link
Member

Choose a reason for hiding this comment

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

You could maybe generalise this by having 'tryAddrInfos' leave out the catch if there's only one element in the list, so that the user always sees one of the connection failures. They might also want to know all addresses that were tried, but that would require more effort with wrapping exceptions etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer to keep all of the dispatch based on number-of-AddrInfos in one place (currently in the case addrinfos of ...).
It is possible to move all that logic to tryAddrInfos, but then you lose the distinction between "no AddrInfos returned by getAddrInfoand "There were multiple addresses, but they all failed". In my PR these cases have differentfail` error messages, and I think that's a good thing.

Copy link
Member

Choose a reason for hiding this comment

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

OK - maybe just include all the addresses in the error message for "they all failed" so the user can then debug by trying them explicitly? It's also ok if you want to leave it as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hsenag I updated the code to show the tried addresses in the error messages.

Copy link
Member

Choose a reason for hiding this comment

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

I prefer to keep all of the dispatch based on number-of-AddrInfos in one place (currently in the case addrinfos of ...).
It is possible to move all that logic to tryAddrInfos, but then you lose the distinction between "no AddrInfos returned by getAddrInfoand "There were multiple addresses, but they all failed". In my PR these cases have differentfail` error messages, and I think that's a good thing.

I don't quite understand that you mean, but I'll note that getAddrInfo never returns an empty list. If there are no addresses it throws and exception, instead.

@frasertweedale
Copy link
Contributor Author

Looks good - is it possible/worthwhile to write a test? (I don't feel strongly either way)

The only way I can think to test it is to start up servers on localhost and try and connect. But it will be brittle or unreliable because some systems executing the tests may be ipv4-only or ipv6-only. Even ignoring that there's significant complexity to test it. I don't want to write it :)

@frasertweedale frasertweedale force-pushed the fix/try-multiple-addrinfo branch 3 times, most recently from 5c41d77 to d39b45c Compare February 18, 2021 05:25
Network/TCP.hs Outdated

-- try multiple addresses; return Just connected socket or Nothing
tryAddrInfos [] = return Nothing
tryAddrInfos (h:t) = fmap Just (connectAddrInfo h) `catchIO_` tryAddrInfos t
Copy link
Member

Choose a reason for hiding this comment

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

You should use try rather than running the remaining connections in an async-exception masked catch handler.

Network/TCP.hs Outdated

let
connectAddrInfo a = do
s <- socket (addrFamily a) Stream defaultProtocol
Copy link
Member

Choose a reason for hiding this comment

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

There's bracketOnError for this resource pattern

@glguy
Copy link
Member

glguy commented Feb 19, 2021

There are standards for connecting to network services in the ipv4/ipv6 world we live in now. One of those is Happy Eyeballs Version 2: Better Connectivity Using Concurrency. This might be more effort that we're willing to go through for HTTP, but I wanted to call it out. I've implemented something close to this in the network connection library I use for my IRC client hookup:openSocket The algorithm tries not to get slowed down (too much) when a user has slow or stalled connectivity on any one address.

@frasertweedale
Copy link
Contributor Author

@glguy yep, the commit message mentions happy eyeballs, and the fact that this ain't it :) I am... not keen to tackle that today. Ideally it would implemented in the network package anyhow. As for your other requests, updated commit coming soon.

Currently, openTCPConnection only considered the first address
returned by getAddrInfo.  This can cause connection failures.  For
example, if the first address returned is an IPv6 address, but the
HTTP server only listens on IPv4, then the connection will be
refused, and the library gives up.

Update openTCPConnection to try each address returned by
getAddrInfo.  This is a naïve algorithm; better approaches are
possible (e.g. see https://en.wikipedia.org/wiki/Happy_Eyeballs).

This change may regress performance of this library if getAddrInfo
returns two or more addresses and several connection attempts time
out (as opposed to "connection refused").  But most users would
probably prefer to succeed slowly than to fail unnecessarily.
@frasertweedale frasertweedale force-pushed the fix/try-multiple-addrinfo branch from d39b45c to 5d132dc Compare February 20, 2021 00:06
@hsenag
Copy link
Member

hsenag commented Mar 15, 2021

Apologies for not merging this sooner, for some reason I had thought there was some open discussion about it. I'll deal with it in the next few days, please feel free to remind me if I don't.

@frasertweedale
Copy link
Contributor Author

@hsenag no worries at all!

@hsenag hsenag merged commit 9bbd9bc into haskell:master Mar 20, 2021
@frasertweedale frasertweedale deleted the fix/try-multiple-addrinfo branch March 21, 2021 03:30
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.

3 participants