Support multiple socket types in the network interface#1144
Support multiple socket types in the network interface#1144stevenengler merged 10 commits intoshadow:devfrom
Conversation
b637204 to
37770a2
Compare
Codecov Report
@@ Coverage Diff @@
## dev #1144 +/- ##
==========================================
+ Coverage 56.62% 56.89% +0.27%
==========================================
Files 137 140 +3
Lines 20067 20181 +114
Branches 4826 4889 +63
==========================================
+ Hits 11362 11482 +120
+ Misses 5731 5671 -60
- Partials 2974 3028 +54
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
| if (new_socket.type == CST_LEGACY_SOCKET) { | ||
| descriptor_ref(new_socket.object.as_legacy_socket); | ||
| } else { | ||
| error("Unexpected CompatSocket type"); |
There was a problem hiding this comment.
Please wait to merge this until after #1148 is merged into dev. (It'll need a follow-up PR to merge master->dev)
There was a problem hiding this comment.
I haven't rebased yet, but will get this functionality when I do.
| object.as_legacy_socket = object_ptr; | ||
| } else { | ||
| error("Unexpected socket pointer tag"); | ||
| abort(); |
There was a problem hiding this comment.
Throughout: can drop the explit abort-after-error after #1148
There was a problem hiding this comment.
I can do this in some places, but when it's a function that has a return value, we sometimes need to abort, otherwise older compilers give a warning about not returning a value from a non-void function.
There was a problem hiding this comment.
Ah, right. We could use the noreturn attribute, but I think we'd need to make error a function (or a macro invoking a noreturn wrapper function). Up to you.
There was a problem hiding this comment.
The error() macro now calls abort at the end.
| CompatSocket compatsocket_fromLegacySocket(Socket* socket); | ||
|
|
||
| /* reference counting */ | ||
| CompatSocket compatsocket_cloneRef(const CompatSocket* socket); |
There was a problem hiding this comment.
Optional: maybe _refAs and _unref to more closely mirror _ref and _unref on other objects?
src/main/host/network_interface.c
Outdated
| g_free(key); | ||
|
|
||
| if(!socket) { | ||
| if (!found) { |
There was a problem hiding this comment.
Can check for socket.type == CST_NONE here instead of tracking a separate flag (assuming you add one as suggested in the other comment)
| #include "main/host/descriptor/socket.h" | ||
| #include "main/utility/tagged_ptr.h" | ||
|
|
||
| enum _CompatSocketTypes { |
There was a problem hiding this comment.
In general it's often useful to have the first enum value be something like '_NONE' or '_INVALID', to ensure a zero-initialized value can't be mistaken for a real value.
It'd be particularly useful for this case, since it'd let you directly represent a converted NULL pointer instead of having to track a separate valid/found flag.
There was a problem hiding this comment.
Added a CST_NONE type.
src/main/host/host.c
Outdated
| in_addr_t bindAddress; | ||
| socket_getSocketName(socket, &bindAddress, NULL); | ||
| if (socket->type == CST_LEGACY_SOCKET) { | ||
| if (!socket_isBound(socket->object.as_legacy_socket)) { |
There was a problem hiding this comment.
Should these be extracted out to compatsocket_isBound and compatsocket_getSocketName?
There was a problem hiding this comment.
I was able to simplify this since we already have a compatsocket_getSocketName(), which already checks and returns false if the socket is bound.
src/main/host/host.c
Outdated
|
|
||
| in_addr_t bindAddress; | ||
| socket_getSocketName(socket, &bindAddress, NULL); | ||
| if (socket->type == CST_LEGACY_SOCKET) { |
There was a problem hiding this comment.
Throughout - consider making these exhaustive switch statements. That way when a new enumerator is added, any such switch statements will become compiler warning (-> error with -werror) until they're updated.
Do be aware that it's a good idea to handle the case where the enum doesn't match any of the enumerator values, though that's unlikely to happen in practice since we're not e.g. deserializing these from some untrusted source.
More on this topic / Blatant self-promotion: https://abseil.io/tips/147
There was a problem hiding this comment.
Done, and also added the -Wswitch flag so that we get warnings about this.
src/main/host/network_interface.c
Outdated
| Socket* socket = priorityqueue_pop(interface->fifoQueue); | ||
| packet = socket_pullOutPacket(socket); | ||
| *socketHandle = *descriptor_getHandleReference((LegacyDescriptor*)socket); | ||
| CompatSocket socket; |
There was a problem hiding this comment.
Throughout - generally a good idea to default-initialize stack-allocated structs as a precaution to avoid use of uninit'd values becoming a "heisenbugs".
Can be done succinctly as CompatSocket socket = {0};
src/main/utility/tagged_ptr.c
Outdated
| #include "main/utility/utility.h" | ||
|
|
||
| // two low-order bits | ||
| const uintptr_t TAG_MASK = (1 << 2) - 1; |
There was a problem hiding this comment.
IIRC on x86_64, objects are 8-byte-aligned, so we could use one more bit. We shouldn't end up needing it for CompatSocket, but maybe worth doing now since this is a standalone utility module.
Definitely double-check first though, or if you don't want to spend time doing that just leave a TODO comment to consider verifying that and adding one more bit.
I see you have a utility_assert that should detect if I'm wrong, but I could imagine some objects being 8-byte-aligned in debug builds, but "packed" into 4-byte-alignment in release builds (where the assert is disabled).
There was a problem hiding this comment.
Good point! I've added explicit error calls here. I've also added an extra bit to the tag. I originally didn't just because we didn't need it, but there should be no harm in making it 3 bits instead of 2.
| }; | ||
|
|
||
| union _CompatSocketObject { | ||
| void* as_none; |
src/main/host/network_interface.c
Outdated
| return false; | ||
| CompatSocket compatSocket = { | ||
| .type = CST_NONE, | ||
| .object.as_none = NULL, |
There was a problem hiding this comment.
Optional - Ok if you'd rather be explicit, but functionally not needed, since members not mentioned in a struct initializer are zero-initialized.
In particular though, being able to explicitly NULL initialize here probably isn't worth having the superfluous as_none in the first place (if that's why you added it). You could drop it and not mention .object here at all, or maybe explicitly init it as .object = {0}.
There was a problem hiding this comment.
Okay I'm zero-initializing the struct now, but still setting the type explicitly to CST_NONE.
| @@ -498,7 +501,7 @@ static Packet* _networkinterface_selectRoundRobin(NetworkInterface* interface, g | |||
|
|
|||
There was a problem hiding this comment.
Codecov says that this entire method, and a fair number of corner cases below, aren't covered by tests. Probably doesn't make sense to add them in this PR, but maybe worth opening an issue to increase coverage for this file?
There was a problem hiding this comment.
(See comment below.)
| @@ -25,7 +25,7 @@ void rrsocketqueue_destroy(RrSocketQueue* self, void (*fn_processItem)(const Com | |||
|
|
|||
| if (fn_processItem != NULL) { | |||
There was a problem hiding this comment.
It looks like this is a new file, or did I miss it getting moved from somewhere else? Does this belong in this PR? It looks like it's untested...?
There was a problem hiding this comment.
This is new code, and any of the round-robin qdisc methods aren't used in our tests. I've verified the round-robin qdisc manually by running phold with --interface-qdisc=RR and the tests pass. I've also now added another phold configuration test using this option.
The only thing weird to me in the coverage results is that I don't think we have any tests where a host has multiple sockets sending data at the same time, so I think it makes sense that _compareSocketTagged() isn't being run, which means there might be an issue with the priority queue. But I haven't made any changes to the priority queue, so I don't know what's going on there._compareSocketTagged() is never being used since the network interface never has more than one socket in a queue at a time.
| CONFIGURATIONS ilibc) | ||
|
|
||
| # Run tests with the round-robin queueing discipline (the current default is fifo). | ||
| add_shadow_tests( |
There was a problem hiding this comment.
Optional: is there a more focused test to which we could add an RR configuration? phold is more of a stress/integration test than a unit test.
There was a problem hiding this comment.
Ideally we'd want to use the RR configuration on a test which uses the network interface a lot, so one which reads and writes a lot of data, and one that sends data on many sockets at once per host. Phold is probably too simple of a test for this as it only writes to one socket at a time, but I figured it's easier for now to use phold rather than writing a new plugin that duplicates a lot of phold's functionality.
6eff4af to
697f3b8
Compare
This adds a
CompatSocketstruct containing a union of socket pointers and a type. The network interface code now works with theseCompatSocketobjects instead of justSocketobjects so that we can support more socket types in the future. To prevent us from having to dynamically allocate memory for theseCompatSocketwhenever we store them (which occurs whenever a plugin binds a socket or writes to a socket), we store a tagged pointer instead that replaces the low-order bits of the socket object pointer with a tag containing the type of socket object.