Repository navigation
#75: Implemented socket abstraction library - #77
sgn4sangar wants to merge 22 commits into
Conversation
7d4b2d4 to
f4ee0d7
Compare
|
| target_compatible_with = ["@platforms//os:linux"], | ||
| ) | ||
|
|
||
| cc_fuzz_test( |
There was a problem hiding this comment.
There are the binary blobs (udf-runner-cpp/v2/fuzz/corpus/socket) but they are not used here!
| #include <cstdint> | ||
| #include <span> | ||
|
|
||
| extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, const std::size_t size) |
There was a problem hiding this comment.
Add a description to this function as the behavior is not very obvious.
As far as I understand, if the written size is less than 4096, it does an early return (line 29), and does not execute the reader test code.
| std::array<int, 2> descriptors{}; | ||
| if (::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, descriptors.data()) != 0) | ||
| { | ||
| return 0; |
There was a problem hiding this comment.
Why is the treated as success? If the socketpair creation fails, shouln't this be treated as an error?
|
Add doxygen comments to the public headers. |
| { | ||
| if (owned_fd < 0) | ||
| { | ||
| throw std::system_error(EBADF, std::generic_category(), "adopt invalid file descriptor"); |
There was a problem hiding this comment.
I think std::invalid_argument fits better here
|
|
||
| bool OwnedFileDescriptor::is_open() const noexcept | ||
| { | ||
| return file_descriptor != -1; |
There was a problem hiding this comment.
According to the coding styles, we want to use this-> to access member variables.
|
|
||
| int OwnedFileDescriptor::native_handle() const noexcept | ||
| { | ||
| return file_descriptor; |
There was a problem hiding this comment.
According to the coding styles, we want to use this-> to access member variables.
| if (this != &other) | ||
| { | ||
| close(); | ||
| file_descriptor = std::exchange(other.file_descriptor, -1); |
There was a problem hiding this comment.
According to the coding styles, we want to use this-> to access member variables.
| std::array<int, 2> first_pipe{}; | ||
| std::array<int, 2> second_pipe{}; | ||
| ASSERT_EQ(::pipe(first_pipe.data()), 0); | ||
| ASSERT_EQ(::pipe(second_pipe.data()), 0); | ||
| OwnedFileDescriptor source = OwnedFileDescriptor::adopt_native_handle(first_pipe[0]); | ||
| OwnedFileDescriptor destination = OwnedFileDescriptor::adopt_native_handle(second_pipe[0]); | ||
| destination = std::move(source); | ||
| EXPECT_EQ(::close(second_pipe[0]), -1); | ||
| EXPECT_EQ(errno, EBADF); | ||
| ASSERT_EQ(::close(destination.release_native_handle()), 0); | ||
| ASSERT_EQ(::close(first_pipe[1]), 0); | ||
| ASSERT_EQ(::close(second_pipe[1]), 0); |
There was a problem hiding this comment.
Maybe check also that destination points to first_pipe[0] handle after the move operation
| std::array<int, 2> pipe_fds{}; | ||
| ASSERT_EQ(::pipe(pipe_fds.data()), 0); | ||
| OwnedFileDescriptor descriptor = OwnedFileDescriptor::adopt_native_handle(pipe_fds[0]); | ||
| OwnedFileDescriptor moved(std::move(descriptor)); |
There was a problem hiding this comment.
Maybe check also that handle of descriptor became invalid here
| ASSERT_EQ(::close(second_pipe[1]), 0); | ||
| } | ||
|
|
||
| TEST(SocketTest, ListenerConnectsAcceptsAndRequiresExplicitCleanup) |
There was a problem hiding this comment.
Maybe move all tests which use the unique_socket_path into a GTest fixture, which provides the temp path, and then automatically cleans up the temporary directory in TearDown().
There was a problem hiding this comment.
You can also use the same class I created in the DB, see
https://github.com/exasol/db/blob/tu.master.SPOT-31038_refactor_udf_fw/Engine/src/exscript/pluggable/server_based/test_utils/temporary_directory_cleanup.h and https://github.com/exasol/db/blob/tu.master.SPOT-31038_refactor_udf_fw/Engine/src/exscript/pluggable/server_based/test_utils/temporary_directory_cleanup.cpp
and here is an example of usage:
https://github.com/exasol/db/blob/e3887e7a372074faada36afe74e1a7f3672899f6/Engine/src/exscript/pluggable/server_based/instance/connection/unix_domain_socket_transport_utest.cpp#L35
There was a problem hiding this comment.
Should we separate the unix-socket related tests from the socket-related tests, and have two test cc files?
| EXPECT_EQ(reader.read_some(eof_buffer), 0); | ||
| } | ||
|
|
||
| TEST(SocketTest, NonblockingReadReportsWouldBlock) |
There was a problem hiding this comment.
This test is a bit confusing. Maybe add an description
| throw std::system_error(EINVAL, std::generic_category(), | ||
| "I/O buffer size is too large"); | ||
| } | ||
| total_size += std::size(buffer); |
There was a problem hiding this comment.
why std::size(buffer) and not buffer.size()?
| total_size += std::size(buffer); | ||
| // POSIX declares iovec::iov_base as void* even for sendmsg(), which does not mutate it. | ||
| iovecs.push_back( | ||
| {const_cast<std::byte*>(std::data(buffer)), std::size(buffer)}); // NOSONAR |
There was a problem hiding this comment.
- why std::data(buffer) and not
buffer.data()? - why
std::size(buffer)and notbuffer.size()?
| { | ||
| if (errno != EINTR) | ||
| { | ||
| throw std::system_error(errno, std::generic_category(), "connect Unix socket"); |
There was a problem hiding this comment.
Is this really a system_error? if connect fails the given address could be wrong.
| OwnedFileDescriptor socket_fd = create_socket(); | ||
| if (::bind(socket_fd.native_handle(), as_socket_address(address), address_length) == -1) | ||
| { | ||
| throw std::system_error(errno, std::generic_category(), "bind Unix socket listener"); |
There was a problem hiding this comment.
same as above: If bind fails, the given address might be wrong
| expect_system_error([path] { static_cast<void>(UnixSocketListener::bind(path)); }, | ||
| std::errc::address_in_use); | ||
| listener.close(); | ||
| listener.unlink_path(); |
There was a problem hiding this comment.
isn't that called in the destructor?
There was a problem hiding this comment.
If the UDF Runner runs in the sandbox (current architecture) the database cleans-up the temp directory where the socket was created after the UDF Runner process died.
=> It's only necessary to clean up in the unit tests.
|
|
||
| UnixSocketListener::~UnixSocketListener() | ||
| { | ||
| close(); |
There was a problem hiding this comment.
I feel like the design does not follow RAII principles, we should also add the "unlink" to the destructor,
this would also thin out the public API
| } | ||
| if (::listen(socket_fd.native_handle(), backlog) == -1) | ||
| { | ||
| throw std::system_error(errno, std::generic_category(), "listen on Unix socket"); |
There was a problem hiding this comment.
bind created the path, listen fails, how do you unlink the path then?
| return 0; | ||
| } | ||
| if (std::array<std::byte, 4096> received{}; | ||
| reader.read_some(std::span(received).first(size)) != size) |
There was a problem hiding this comment.
I think it's a valid use case to read fewer bytes.




fixes #75