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
Add LibUDPard demo #16
Conversation
👀 |
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.
Will continue this review tomorrow but note that I get integer narrowing warnings from libudpard when building on 64-bit systems. Have you tried building on linux 64?
libudpard/src/memory_block.h
Outdated
_Alignas(max_align_t) static uint_least8_t _name##_pool[(_block_size_bytes) * (_block_count)]; \ | ||
struct MemoryBlockAllocator _name = memoryBlockInit(sizeof(_name##_pool), &_name##_pool[0], (_block_size_bytes)) | ||
|
||
/// The user shall not attempt to change any of the fields manually. |
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.
OH? Is that a challenge? Perhaps I will change these, thank-you-very-much!!!
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 was going for the record for the smallest possible implementation of a block pool allocator. Counting 101 LoC at the moment.
641d9db
to
c066c49
Compare
Very exciting to see! Will review more thoroughly tomorrow |
ok = ok && (setsockopt(self->fd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) == 0); | ||
#endif | ||
// Binding to the multicast group address is necessary on GNU/Linux: https://habr.com/ru/post/141021/ | ||
// Binding to a multicast address is not allowed on Windows, and it is not necessary there; |
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...this will fail on Windows?
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.
Yes, this will fail on Windows. To make it work there, the binding has to be conditional. I could throw in some ifdefs in there but we don't really want to test it against Windows, do we?
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.
It'd be nice if this worked on Windows, yes.
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 added the required preprocessor check but this is not yet tested on Windows. I'm afraid that ensuring compatibility with Windows is a rather significant chunk of work.
… not usable here as it is not guaranteed to be defined on Linux.
/// Enable SO_REUSEPORT. | ||
#define _DEFAULT_SOURCE // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) |
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.
Does the SO_REUSEPORT
comment relate to defining DEFAULT_SOURCE
?
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.
Yes. _DEFAULT_SOURCE
is a feature test macro that enables certain non-POSIX extensions that are disabled by default when a strict standard mode is used; in this case, -std=c99
. See https://man7.org/linux/man-pages/man7/feature_test_macros.7.html
int16_t res = -EINVAL; | ||
if ((self != NULL) && (local_iface_address > 0)) | ||
{ | ||
self->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
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.
Should we use SOCK_NONBLOCK
? Guess that's more up to customers if they want to use that or not
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.
On line 48, we have fcntl(self->fd, F_SETFL, O_NONBLOCK)
. We could move it here as well if desired.
} | ||
} | ||
|
||
static void cbOnNodeIDAllocationData(struct Subscriber* const self, struct UdpardRxTransfer* const transfer) |
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.
What does cb
stand for?
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.
Oh it stands for callback
} | ||
|
||
// Process the RX sockets that became readable. | ||
// The time has to be re-sampled because the blocking wait may have taken a long time. |
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.
What's the benefit of having the blocking wait?
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.
It's just standard IO multiplexing. We suspend the thread when there's nothing to do. It is unblocked either when there is a socket to handle or the application needs to do something.
libudpard_demo/src/main.c
Outdated
continue; | ||
} | ||
// Pass the data buffer into LibUDPard for further processing. It takes ownership of the buffer. | ||
if (rx_aw[i].user_reference != NULL) // This is used to differentiate subscription sockets from RPC sockets. |
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 there not a better way to differentiate between messages and services than checking whether the user reference is NULL or not?
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 ask myself the same question. We need to keep state per awaitable to differentiate between the two. We can't subtype it to add custom state because that breaks the array layout. All of the better solutions I could come up with are various shades of bad:
- Pass awaitables as an array of pointers -- requires an extra array.
- Use a linked list -- results in a clumsy API.
- Add the required fields to the awaitable type -- breaks encapsulation.
I guess I better add an explanatory comment about this.
} // Otherwise, it's a request from another allocation client node, or we already have a node-ID. | ||
} | ||
|
||
static void cbOnMyData(struct Subscriber* const self, struct UdpardRxTransfer* const transfer) |
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 could use the same callback for multiple subscribers, correct?
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.
ye
/// `____/ .___/`___/_/ /_/`____/`__, / .___/_/ /_/`__,_/_/ | ||
/// /_/ /____/_/ | ||
/// | ||
/// In Cyphal, registers are named values that can be read and possibly written over the network. They are used |
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 everything is configured statically, are registers still needed?
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.
No, they are quite optional. The only required thing is the heartbeat publisher.
General feeling is that |
The code is a little sloppy compared to the library itself but it should be adequate for the demo.
I moved the platform-specific parts -- the UDP stack and the storage I/O -- into separate modules to make the API surface clearly visible. This also simplifies porting if one wants to run this demo on an MCU.
I avoided building strong abstractions on top of LibUDPard because that may obscure the API of the library and attract attention of the reader to the features of the demo instead of those of the library itself. The flipside is that
main.c
has to be fairly large.The Cavl library was added by copying the header file instead of adding the submodule because the entire library is just a single file alone so the submodule approach seemed like an overkill. We can change this if necessary, though. See a related question here: OpenCyphal/libudpard#11