-
Notifications
You must be signed in to change notification settings - Fork 251
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
update socket config and create builder pattern #3929
base: master
Are you sure you want to change the base?
Conversation
707a6f2
to
e8410b7
Compare
net-utils/src/lib.rs
Outdated
} | ||
|
||
#[cfg(not(any(windows, target_os = "ios")))] | ||
impl Default for SocketConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty sure this was my doing, but i don't really think a "default" socket config makes sense. default makes it too easy for people to instantiate a thing without thinking and in the case of these sockets, i think we want people to think about the config they're specifying
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the idea that we only expose a SocketConfig
constructor that takes in: reuseport
, send_buffer_size
, and receive_buffer_size
? and the user would have to explicitly set these?
I worry people would set these incorrectly. and having some default at least falls back on the original settings (using the equivalent buffer sizes previously set using sysctl
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we don't have a default socket config, should we also not have DEFAULT_RECV_BUFFER_SIZE
and DEFAULT_SEND_BUFFER_SIZE
? just worried people are going to complain that they have to set these manually. And if they have to set them manually, they may just pick some large number (> 128MB) that ends up defeating the whole point of these PRs.
Also, the kernel doubles the buffer sizes passed in via setsockopt
. So someone trying to set a buffer size of 128MB is unknowingly allocating 256MB to their sockets per side.
We could force users to set buffer sizes within some range. Minimum ~4MB and max 128MB? That way we limit misconfigurations to some extent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of agree with Greg, if we're going to have default constants for fields, then I don't think it is too much of a stretch to have a default for the struct that contains those fields.
An alternative could be instead of Default
implementation, we could have default_ro
, default_wo
and default_rw
that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya we could have these default_ro
, default_wo
, default_rw
. and then can have an option to set reuseport
. we can also have the scaffold for setting buffer sizes directly like:
// set read buffer to 16MB and reuseport to true
let socket = SocketConfig::default_ro().recv_buffer_size(16 * 1024 * 1024).reuseport(true);
For this PR we are not changing any of the actual buffer sizes. So, I propose for this PR, we provide default_wr
that will set buffer sizes to the current 128MB. We leave out default_ro()
and default_wo()
on this PR since these defaults don't really make sense if we're not changing the buffer sizes on this PR
And then we at least have the functionality for the next PR where we can add default_ro()
and default_wo()
and set the buffer sizes
does that sound good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me; thoughts @t-nelson ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more defaults are the wrong direction IMO. how about this concession..
#[derive(Default)]
pub struct SocketConfig {
reuseport: bool,
recv_buffer_size: Option<usize>,
send_buffer_size: Option<usize>,
}
then when we build the socket, only even call setsockopt()
when the field is Some(size)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this case we just leave the socket buffer size set at whatever the default OS buffer size is? at the end of the day, someone is setting a default. it's either us or the OS. I'm ok with this idea tho
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah we have little control over what the os does. odds are we shouldn't have custom "default" in the first place. the value from the tuning guide was just cribbed out of some blog post and tossed in iirc. no rigor to it. we should be tuning the socket buffers intelligently or not allocating sockets at all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about this concession..
Works for me
e9c753e
to
cd98ecf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think we're getting close; just one question and a couple nits
gossip/src/cluster_info.rs
Outdated
let tpu_vote_quic_config = SocketConfig::default().reuseport(true); | ||
let (tpu_vote_port, tpu_vote) = | ||
Self::bind_with_config(bind_ip_addr, port_range, tpu_vote_udp_config.clone()); | ||
// using udp port for quic really because we need to reusport set to false, since Self::bind() defaults to false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// using udp port for quic really because we need to reusport set to false, since Self::bind() defaults to false | |
// using udp port for quic really because we need to set reusport to false, since Self::bind() defaults to false |
nit: grammar
Also, can provide a little more detail on this piece; seems a little tricky
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly, no idea why i wrote this. doesn't really make sense. but happy to get some more details on this
gossip/src/cluster_info.rs
Outdated
let (_, broadcast) = Self::bind(bind_ip_addr, port_range); | ||
let (_, ancestor_hashes_requests) = Self::bind(bind_ip_addr, port_range); | ||
let (_, ancestor_hashes_requests_quic) = Self::bind(bind_ip_addr, port_range); | ||
let write_only_socket_config = SocketConfig::default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change to wo
is a change in behavior right in this PR right ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ya i forgot to change this back. i am going to change this. and set as just socket_config
and also want to avoid using "read only" or "write only" since that's not technically correct with quic sockets.
gossip/src/cluster_info.rs
Outdated
multi_bind_in_range(bind_ip_addr, port_range, num_tvu_sockets.get()) | ||
.expect("tvu multi_bind"); | ||
let (tvu_quic_port, tvu_quic) = Self::bind(bind_ip_addr, port_range); | ||
let tvu_config = SocketConfig::default().reuseport(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: For consistency with higher in this function, it seems we might name this tvu_udp_config
. There are a couple cases below too; I don't feel too strongly which way we go but I do prefer consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so was thinking for this PR, since we are just adding this default, we just stick with:
let socket_config = SocketConfig::default();
// and
let socket_config_reuseport = SocketConfig::default().reuseport(true)
for the next PR when we change buffer sizes, maybe it makes sense to label these explicitly like tvu_config
, tpu_forwards_config
, etc. and here we would be setting buffer sizes.
b84b41e
to
296b463
Compare
net-utils/src/lib.rs
Outdated
#[derive(Clone, Debug, Default)] | ||
pub struct SocketConfig { | ||
pub reuseport: bool, | ||
reuseport: bool, | ||
#[cfg(not(any(windows, target_os = "ios")))] | ||
recv_buffer_size: Option<usize>, | ||
#[cfg(not(any(windows, target_os = "ios")))] | ||
send_buffer_size: Option<usize>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can add #[derive(Copy)]
here and wouldn't need config.clone()
elsewhere in the code.
Also, using #[cfg(..)]
for struct fields probably isn't the best.
The fewer code differences we have across platforms is better, and here I don't think you need to change struct definition.
net-utils/src/lib.rs
Outdated
// allow here to supress unused warnings from windows/ios builds | ||
#[allow(unused_mut, unused_variables)] | ||
pub fn recv_buffer_size(mut self, size: usize) -> Self { | ||
#[cfg(not(any(windows, target_os = "ios")))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly here (and perhaps other places in the code) there is no harm keeping the code the same across platforms.
so #[cfg(...)]
here is probably unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Outside of the scope of this PR, but there is a little duplication in gossip/src/cluster_info.rs
. For example, prior to this PR, this exact block shows up twice:
let (_, repair) = Self::bind(bind_ip_addr, port_range);
let (_, repair_quic) = Self::bind(bind_ip_addr, port_range);
let (serve_repair_port, serve_repair) = Self::bind(bind_ip_addr, port_range);
let (serve_repair_quic_port, serve_repair_quic) = Self::bind(bind_ip_addr, port_range);
Maybe as the SocketConfig
tuning becomes more sophisticated in subsequent PR(s), it will make sense to refactor some stuff in a common helper (ie bind_repair()
).
Wouldn't be the worst thing to get another approval from @behzadnouri or @t-nelson to make sure I didn't miss anything
#[cfg(not(any(windows, target_os = "ios")))] | ||
const DEFAULT_RECV_BUFFER_SIZE: usize = 64 * 1024 * 1024; // 64 MB - Doubled to 128MB by the kernel | ||
#[cfg(not(any(windows, target_os = "ios")))] | ||
const DEFAULT_SEND_BUFFER_SIZE: usize = 64 * 1024 * 1024; // 64 MB - Doubled to 128MB by the kernel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is line with intent from #3929 (comment), but just calling out this a potential change in behavior. I'm fine with it tho as:
- People who aren't knowledgable on this probably copy/pasted tuning guide
- People who are knowledgable probably set custom values
- Subsequent PR(s) will set intelligent values, and those will hopefully come in quicker with the groundwork laid in this PR
Problem
We allocate large send and receive buffers for all sockets. However, not all sockets read and write equally from/to their buffers. In fact, 6 sockets are primarily Read and 2 are primarily Write. Meaning we are allocating memory that is never used.
Summary of Changes
preparation for reducing socket buffer sizes. This PR leaves socket buffer sizes as is.
SocketConfig
to use builder patternnet-utils
methods that use defaultSocketConfig
Services/Sockets
Read/Write
Primarily Read
Primarily Write
Follow Up PR: