-
Notifications
You must be signed in to change notification settings - Fork 0
Implement aborting read/write #6
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
base: main
Are you sure you want to change the base?
Conversation
- 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.
…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.
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.
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
serialAbortReadandserialAbortWritefunctions to trigger aborts - Modified
serialReadandserialWriteto check for abort signals during polling - Removed blocking
tcdrain()call fromserialWriteto 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.
| 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; |
Copilot
AI
Dec 16, 2025
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.
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.
| // Drain any pending bytes from a non-blocking FD (used for abort pipes). | ||
| inline auto drainNonBlockingFd(int file_descriptor) -> void |
Copilot
AI
Dec 16, 2025
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.
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.
src/detail/posix_helpers.hpp
Outdated
| const int poll_result = poll(fds, nfds, timeout_ms); | ||
| if (poll_result < 0) | ||
| { | ||
| return -1; | ||
| } |
Copilot
AI
Dec 16, 2025
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.
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.
…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.
No description provided.