Skip to content

Conversation

@Katze719
Copy link
Member

No description provided.

- Introduced `serialAbortRead` and `serialAbortWrite` functions to handle abort requests for serial operations.
- Implemented `abort_registry` to manage abort pipes for serial file descriptors, including functions to register, unregister, and retrieve abort pipes.
- Updated existing serial read and write functions to support abort functionality using the new abort pipes.
- Enhanced `serial_close` and `serial_open` to manage abort pipes during the lifecycle of serial connections.
- Added utility functions for non-blocking read operations from abort pipes to ensure responsiveness during serial communication.
…and non-blocking operations

- Updated `serial_abort_read.cpp` and `serial_abort_write.cpp` to use `num_written` for clarity in write operations.
- Enhanced `serial_read.cpp` with new non-blocking read utility functions and improved error handling for read operations.
- Introduced `readUntilNotReady` and `tryReadOnceNonBlocking` to streamline the reading process and handle abort scenarios more effectively.
- Refactored `abort_registry.cpp` to utilize `std::array` for pipe management and improved mutex naming for better readability.
- Introduced `serialAbortRead` and `serialAbortWrite` functions to handle abort requests for serial operations.
- Updated the GitHub workflows for C++ and Deno tests to include the `SERIAL_TEST_SKIP_INIT_DELAY` environment variable.
- Enhanced integration tests to assert the existence of new abort functions.
- Added a new test file `test_abort.cpp` to validate the behavior of aborting blocking read and write operations in a multi-threaded context.
- Modified existing tests to accommodate the new abort functionality and ensure proper error handling during serial operations.
@Katze719 Katze719 requested a review from Copilot December 16, 2025 22:55
@Katze719 Katze719 marked this pull request as draft December 16, 2025 22:55
@Katze719 Katze719 self-assigned this Dec 16, 2025
…lity

- Removed Linux-specific code for pipe creation in `abort_registry.cpp`.
- Simplified the implementation to use a standard pipe creation method, enhancing portability across different platforms.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a mechanism to abort blocking read and write operations on serial ports from other threads. The implementation uses POSIX pipes to signal abort requests and integrates them with the existing poll-based timeout mechanism. The abort functionality is registered when a serial port is opened and cleaned up when it is closed.

Key changes:

  • Added abort registry infrastructure to manage abort pipes per file descriptor
  • Implemented serialAbortRead and serialAbortWrite functions to trigger aborts
  • Modified serialRead and serialWrite to check for abort signals during polling
  • Removed blocking tcdrain() call from serialWrite to enable abort functionality

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/detail/abort_registry.hpp Defines AbortPipes structure and functions for managing abort pipes per FD
src/detail/abort_registry.cpp Implements abort pipe registration, cleanup, and lookup with mutex protection
src/detail/posix_helpers.hpp Adds drainNonBlockingFd and waitFdReadyOrAbort helpers for abort support
src/serial_abort_read.cpp Implements serialAbortRead by writing to the read abort pipe
src/serial_abort_write.cpp Implements serialAbortWrite by writing to the write abort pipe
src/serial_open.cpp Registers abort pipes for the serial FD after successful configuration
src/serial_close.cpp Unregisters and closes abort pipes before closing the serial FD
src/serial_read.cpp Refactored to use waitFdReadyOrAbort and handle abort signals
src/serial_write.cpp Modified to use waitFdReadyOrAbort, handle aborts, and removed tcdrain()
tests/test_abort.cpp Comprehensive unit tests for abort read/write functionality
tests/test_serial_arduino.cpp Added abort read test and environment variable for skipping init delay
integration_tests/ffi_bindings.ts Added FFI bindings for serialAbortRead and serialAbortWrite
integration_tests/integration_test.ts Added assertions for abort function symbols
.github/workflows/cpp-tests.yml Added SERIAL_TEST_SKIP_INIT_DELAY environment variable
.github/workflows/deno-tests.yml Added SERIAL_TEST_SKIP_INIT_DELAY environment variable
Comments suppressed due to low confidence (1)

src/detail/abort_registry.cpp:127

  • There is a race condition between getAbortPipesForFd and unregisterAbortPipesForFd. The function returns a copy of AbortPipes containing file descriptors, but releases the lock before the caller uses those descriptors. If another thread calls unregisterAbortPipesForFd and closes the pipes after the lock is released but before the caller uses the returned file descriptors, the caller will attempt to read from or write to closed file descriptors. This can lead to undefined behavior, including writing to file descriptors that have been reused for other purposes. Consider using reference counting or another synchronization mechanism to ensure the pipes remain valid while in use.
    {
        return std::nullopt;
    }

    std::lock_guard<std::mutex> lock(g_abort_mutex);
    auto iter = g_abort_by_fd.find(fd);
    if (iter == g_abort_by_fd.end())
    {
        return std::nullopt;
    }
    return iter->second;
}
} // namespace cpp_bindings_linux::detail


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +186 to +199
if (abort_fd >= 0 && ((fds[1].revents & POLLIN) != 0))
{
return 2;
}

if (for_read && ((fds[0].revents & POLLIN) != 0))
{
return 1;
}
if (!for_read && ((fds[0].revents & POLLOUT) != 0))
{
return 1;
}
return 0;
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

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

The poll result checking does not handle error conditions (POLLERR, POLLHUP, POLLNVAL) on the file descriptors. When poll() returns with these flags set, it indicates an error condition on the file descriptor. The current implementation only checks for POLLIN/POLLOUT and might return 0 (timeout) even when an error occurred. Consider checking for error flags and returning -1 or handling them appropriately to avoid silent failures.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +99
// Drain any pending bytes from a non-blocking FD (used for abort pipes).
inline auto drainNonBlockingFd(int file_descriptor) -> void
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

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

The function comment should clarify that this function is specifically designed for draining abort pipes and does not invoke error callbacks. It also silently ignores errors other than EAGAIN/EWOULDBLOCK/EINTR, which is intentional for cleanup operations but should be documented to prevent confusion.

Copilot uses AI. Check for mistakes.
Comment on lines 176 to 180
const int poll_result = poll(fds, nfds, timeout_ms);
if (poll_result < 0)
{
return -1;
}
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

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

The poll() system call can be interrupted by signals and return -1 with errno set to EINTR. This should be handled by retrying the poll() call in a loop, similar to how EINTR is handled in other parts of the codebase (e.g., drainNonBlockingFd, serialAbortRead, serialAbortWrite). Without this, an abort operation could fail unexpectedly if a signal arrives during polling.

Copilot uses AI. Check for mistakes.
…ialWrite function for improved timeout handling

- Eliminated the `SERIAL_TEST_SKIP_INIT_DELAY` environment variable from both C++ and Deno test workflows.
- Refactored the `serialWrite` function to enhance timeout management, allowing for a multiplier effect on subsequent byte writes.
- Updated error handling and added support for abort scenarios during write operations.
- Improved test cases to reflect changes in the serial communication logic and ensure robust functionality.
…e test coverage

- Renamed `readUntilNotReady` to `readUntilTimeoutOrFull` for clarity and updated its parameters to include a per-iteration timeout.
- Modified the `serialRead` function to utilize the new read function, allowing for more flexible timeout management based on a multiplier.
- Added a new utility function `drainInput` in tests to facilitate input handling during tests.
- Introduced new test cases to validate timeout behavior when reading from the serial interface, ensuring robustness in scenarios with no data available.
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.

2 participants