diff --git a/src/serialport.cpp b/src/serialport.cpp index c03f38f1..697e3a55 100644 --- a/src/serialport.cpp +++ b/src/serialport.cpp @@ -132,6 +132,11 @@ Napi::Value Close(const Napi::CallbackInfo& info) { CloseBaton* baton = new CloseBaton(callback); baton->fd = info[0].ToNumber().Int64Value();; +#ifndef WIN32 + // Mark as closing before queueing worker so in-flight drain workers can exit. + markClosingFd(baton->fd); +#endif + baton->Queue(); return env.Undefined(); } diff --git a/src/serialport.h b/src/serialport.h index 6489b417..45fbe879 100644 --- a/src/serialport.h +++ b/src/serialport.h @@ -199,4 +199,9 @@ struct FlushBaton : VoidBaton { int setup(int fd, OpenBaton *data); int setBaudRate(ConnectionOptions *data); + +#ifndef WIN32 +void markClosingFd(int fd); +void unmarkClosingFd(int fd); +#endif #endif // PACKAGES_SERIALPORT_SRC_SERIALPORT_H_ diff --git a/src/serialport_unix.cpp b/src/serialport_unix.cpp index 261342a0..08b38150 100644 --- a/src/serialport_unix.cpp +++ b/src/serialport_unix.cpp @@ -2,10 +2,12 @@ #include "serialport.h" #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #ifdef __APPLE__ #include @@ -28,7 +30,25 @@ #include "serialport_linux.h" #endif -int ToStopBitsConstant(SerialPortStopBits stopBits); +int ToStopBitsConstant(SerialPortStopBits stopBits); +static const int DRAIN_POLL_INTERVAL_MS = 10; +static std::mutex gClosingFdsMutex; +static std::unordered_set gClosingFds; + +void markClosingFd(int fd) { + std::lock_guard lock(gClosingFdsMutex); + gClosingFds.insert(fd); +} + +void unmarkClosingFd(int fd) { + std::lock_guard lock(gClosingFdsMutex); + gClosingFds.erase(fd); +} + +bool isClosingFd(int fd) { + std::lock_guard lock(gClosingFdsMutex); + return gClosingFds.find(fd) != gClosingFds.end(); +} int ToBaudConstant(int baudRate) { switch (baudRate) { @@ -358,13 +378,22 @@ int setBaudRate(ConnectionOptions *data) { return 1; } -void CloseBaton::Execute() { - - if (-1 == close(fd)) { - snprintf(errorString, sizeof(errorString), "Error: %s, unable to close fd %d", strerror(errno), fd); - this->SetError(errorString); - } -} +void CloseBaton::Execute() { + markClosingFd(fd); + + // Avoid blocking close() on tty drivers that wait for pending TX. + int flags = fcntl(fd, F_GETFL); + if (flags != -1) { + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + tcflush(fd, TCOFLUSH); + + if (-1 == close(fd)) { + snprintf(errorString, sizeof(errorString), "Error: %s, unable to close fd %d", strerror(errno), fd); + this->SetError(errorString); + } + unmarkClosingFd(fd); +} void SetBaton::Execute() { @@ -472,11 +501,35 @@ void FlushBaton::Execute() { } } -void DrainBaton::Execute() { - - if (-1 == tcdrain(fd)) { - snprintf(errorString, sizeof(errorString), "Error: %s, cannot drain", strerror(errno)); - this->SetError(errorString); - return; - } +void DrainBaton::Execute() { +#if defined(__linux__) && defined(TIOCOUTQ) + while (true) { + if (isClosingFd(fd)) { + snprintf(errorString, sizeof(errorString), "Error: drain canceled by close"); + this->SetError(errorString); + return; + } + + int pendingBytes = 0; + if (-1 == ioctl(fd, TIOCOUTQ, &pendingBytes)) { + if (errno == ENOTTY || errno == EINVAL) { + break; + } + snprintf(errorString, sizeof(errorString), "Error: %s, cannot drain", strerror(errno)); + this->SetError(errorString); + return; + } + if (pendingBytes <= 0) { + return; + } + usleep(DRAIN_POLL_INTERVAL_MS * 1000); + } +#endif + // TODO: On Unix platforms without TIOCOUTQ, drain still falls back to tcdrain(). + // Add a cancellable drain strategy for those targets. + if (-1 == tcdrain(fd)) { + snprintf(errorString, sizeof(errorString), "Error: %s, cannot drain", strerror(errno)); + this->SetError(errorString); + return; + } }