Skip to content

#75: Implemented socket abstraction library - #77

Open
sgn4sangar wants to merge 22 commits into
mainfrom
feature/75_implement_socket_abstraction_library
Open

sgn4sangar wants to merge 22 commits into
mainfrom
feature/75_implement_socket_abstraction_library

Conversation

@sgn4sangar

Copy link
Copy Markdown
Collaborator

fixes #75

@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval September 30, 2026 07:16 — with GitHub Actions Waiting
Comment thread udf-runner-cpp/v2/include/exasol/udf/v2/socket/socket.hpp Outdated
@sgn4sangar
sgn4sangar force-pushed the feature/75_implement_socket_abstraction_library branch from 7d4b2d4 to f4ee0d7 Compare October 3, 2026 07:42
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 3, 2026 07:42 — with GitHub Actions Waiting
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 5, 2026 09:27 — with GitHub Actions Waiting
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 5, 2026 11:39 — with GitHub Actions Waiting
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 5, 2026 12:19 — with GitHub Actions Waiting
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 5, 2026 14:22 — with GitHub Actions Waiting
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 6, 2026 05:00 — with GitHub Actions Waiting
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 7, 2026 06:11 — with GitHub Actions Waiting
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
64.3% Coverage on New Code (required ≥ 80%)
1 New Code Smells (required ≤ 0)
D Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

target_compatible_with = ["@platforms//os:linux"],
)

cc_fuzz_test(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why is the treated as success? If the socketpair creation fails, shouln't this be treated as an error?

@tomuben

tomuben commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Add doxygen comments to the public headers.

Comment thread udf-runner-cpp/v2/socket/socket.cc Outdated
{
if (owned_fd < 0)
{
throw std::system_error(EBADF, std::generic_category(), "adopt invalid file descriptor");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think std::invalid_argument fits better here

Comment thread udf-runner-cpp/v2/socket/socket.cc Outdated

bool OwnedFileDescriptor::is_open() const noexcept
{
return file_descriptor != -1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

According to the coding styles, we want to use this-> to access member variables.

Comment thread udf-runner-cpp/v2/socket/socket.cc Outdated

int OwnedFileDescriptor::native_handle() const noexcept
{
return file_descriptor;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

According to the coding styles, we want to use this-> to access member variables.

Comment thread udf-runner-cpp/v2/socket/socket.cc Outdated
if (this != &other)
{
close();
file_descriptor = std::exchange(other.file_descriptor, -1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

According to the coding styles, we want to use this-> to access member variables.

Comment on lines +86 to +97
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe check also that handle of descriptor became invalid here

Comment thread udf-runner-cpp/v2/socket/socket_test.cc Outdated
ASSERT_EQ(::close(second_pipe[1]), 0);
}

TEST(SocketTest, ListenerConnectsAcceptsAndRequiresExplicitCleanup)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we separate the unix-socket related tests from the socket-related tests, and have two test cc files?

Comment thread udf-runner-cpp/v2/socket/socket_test.cc Outdated
EXPECT_EQ(reader.read_some(eof_buffer), 0);
}

TEST(SocketTest, NonblockingReadReportsWouldBlock)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test is a bit confusing. Maybe add an description

Comment thread udf-runner-cpp/v2/socket/unix_socket.cc Outdated
throw std::system_error(EINVAL, std::generic_category(),
"I/O buffer size is too large");
}
total_size += std::size(buffer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why std::size(buffer) and not buffer.size()?

Comment thread udf-runner-cpp/v2/socket/unix_socket.cc Outdated
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • why std::data(buffer) and not buffer.data()?
  • why std::size(buffer) and not buffer.size()?

{
if (errno != EINTR)
{
throw std::system_error(errno, std::generic_category(), "connect Unix socket");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same as above: If bind fails, the given address might be wrong

Comment thread udf-runner-cpp/v2/socket/socket_test.cc Outdated
expect_system_error([path] { static_cast<void>(UnixSocketListener::bind(path)); },
std::errc::address_in_use);
listener.close();
listener.unlink_path();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isn't that called in the destructor?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it's a valid use case to read fewer bytes.

@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 9, 2026 11:18 — with GitHub Actions Waiting
@sgn4sangar
sgn4sangar requested a deployment to v2-fuzzing-pr-approval October 9, 2026 16:22 — with GitHub Actions Waiting

This branch is waiting to be deployed

1 waiting deployment
v2-fuzzing-pr-approval — 4df8cecb Waiting Oct 9, 2026 by sgn4sangar via pr_approval #157
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.

Implement Socket Abstraction library

3 participants