Skip to content

Commit 9025946

Browse files
committed
refactor(TCPSocket): express stop via SocketState instead of a boolean
Replace the stop_requested_ flag and requestStop()/clearStop() API with an enriched SocketState machine (Connecting/Connected/Reconnecting/LostConnection/ Disconnecting/Disconnected). The public lifecycle is now just connect() and disconnect(): connect() implicitly clears a prior deliberate stop, so callers no longer have to remember a separate clear step. The deliberate-stop set {Disconnecting, Disconnected} is sticky and is never overwritten by the connect/retry loop (setup()) or close(), and is cleared only by an explicit connect(). The in-loop auto-reconnect (reconnect()) never clears it, keeping ~PrimaryClient()/~RTDEClient() teardown race-free (the original #368 fix). The transient-drop state is renamed Disconnected -> LostConnection; SocketState::Disconnected is repurposed for the deliberate stop. Update URStream, URProducer, PrimaryClient and RTDEClient to the new API, and adjust the affected unit tests.
1 parent 763e77a commit 9025946

10 files changed

Lines changed: 253 additions & 143 deletions

File tree

include/ur_client_library/comm/producer.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class URProducer : public IProducer<T>
7474
continue;
7575
}
7676

77-
if (stream_.closed())
77+
if (stream_.closed() || stream_.stopRequested())
7878
return false;
7979

8080
if (on_reconnect_cb_)
@@ -92,16 +92,16 @@ class URProducer : public IProducer<T>
9292
// to 120 s while this thread sleeps here.
9393
const auto sleep_slice = std::chrono::milliseconds(100);
9494
const auto sleep_total = std::chrono::duration_cast<std::chrono::milliseconds>(timeout_);
95-
for (auto slept = std::chrono::milliseconds(0); slept < sleep_total && running_ && !stream_.closed();
96-
slept += sleep_slice)
95+
for (auto slept = std::chrono::milliseconds(0);
96+
slept < sleep_total && running_ && !stream_.closed() && !stream_.stopRequested(); slept += sleep_slice)
9797
{
9898
std::this_thread::sleep_for(sleep_slice);
9999
}
100100

101-
if (!running_ || stream_.closed())
101+
if (!running_ || stream_.closed() || stream_.stopRequested())
102102
return false;
103103

104-
if (stream_.connect())
104+
if (stream_.reconnect())
105105
continue;
106106

107107
auto next = timeout_ * 2;
@@ -133,9 +133,6 @@ class URProducer : public IProducer<T>
133133
void setupProducer(const size_t max_num_tries = 0,
134134
const std::chrono::milliseconds reconnection_time = std::chrono::seconds(10)) override
135135
{
136-
// Clear any cancellation request left over from a previous teardown so the stream can be
137-
// (re)used. Safe here because this runs on the controlling thread before the producer loop.
138-
stream_.clearStop();
139136
timeval tv;
140137
tv.tv_sec = 1;
141138
tv.tv_usec = 0;

include/ur_client_library/comm/stream.h

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,32 @@ class URStream : public TCPSocket
6363
bool connect(const size_t max_num_tries = 0,
6464
const std::chrono::milliseconds reconnection_time = std::chrono::seconds(10))
6565
{
66-
return TCPSocket::setup(host_, port_, max_num_tries, reconnection_time);
66+
return TCPSocket::connect(host_, port_, max_num_tries, reconnection_time);
6767
}
6868

6969
/*!
70-
* \brief Disconnects from the configured socket.
70+
* \brief Re-establishes the connection after an unexpected drop, without clearing a deliberate
71+
* disconnect(). Used by the automatic reconnect path.
72+
*
73+
* \param max_num_tries Maximum number of connection attempts before failing. Unlimited when 0.
74+
* \param reconnection_time time in between connection attempts to the server
75+
*
76+
* \returns True on success, false if it could not reconnect or a deliberate disconnect() is in
77+
* effect
78+
*/
79+
bool reconnect(const size_t max_num_tries = 0,
80+
const std::chrono::milliseconds reconnection_time = std::chrono::seconds(10))
81+
{
82+
return TCPSocket::reconnect(host_, port_, max_num_tries, reconnection_time);
83+
}
84+
85+
/*!
86+
* \brief Deliberately disconnects from the configured socket, leaving it ready to connect again.
7187
*/
7288
void disconnect()
7389
{
7490
URCL_LOG_DEBUG("Disconnecting from %s:%d", host_.c_str(), port_);
75-
TCPSocket::close();
91+
TCPSocket::disconnect();
7692
}
7793

7894
/*!
@@ -83,6 +99,16 @@ class URStream : public TCPSocket
8399
return getState() == SocketState::Closed;
84100
}
85101

102+
/*!
103+
* \brief Returns whether a deliberate disconnect() is in progress or in effect (the socket will
104+
* not auto-reconnect until connect() is called again).
105+
*/
106+
bool stopRequested()
107+
{
108+
const SocketState s = getState();
109+
return s == SocketState::Disconnecting || s == SocketState::Disconnected;
110+
}
111+
86112
/*!
87113
* \brief Reads a full UR package out of a socket. For this, it looks into the package and reads
88114
* the byte length from the socket directly. It returns as soon as all bytes for the package are

include/ur_client_library/comm/tcp_socket.h

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ namespace comm
3636
*/
3737
enum class SocketState
3838
{
39-
Invalid, ///< Socket is initialized or setup failed
40-
Connected, ///< Socket is connected and ready to use
41-
Disconnected, ///< Socket is disconnected and cannot be used
42-
Closed ///< Connection to socket got closed
39+
Invalid, ///< Socket is initialized but was never connected
40+
Connecting, ///< A first-time connect() attempt is in progress
41+
Connected, ///< Socket is connected and ready to use
42+
LostConnection, ///< Connection dropped unexpectedly; auto-reconnect is expected to pick it up
43+
Reconnecting, ///< An automatic reconnect attempt (after a drop) is in progress
44+
Disconnecting, ///< A deliberate disconnect() is in progress
45+
Disconnected, ///< Deliberately disconnected; will NOT auto-reconnect until connect() is called
46+
Closed ///< Neutral low-level close (clearable by a subsequent (re)connect)
4347
};
4448

4549
/*!
@@ -53,18 +57,25 @@ class TCPSocket
5357
std::chrono::milliseconds reconnection_time_;
5458
bool reconnection_time_modified_deprecated_ = false;
5559

56-
// Cancellation token used to abort a connection attempt that is currently in
57-
// progress (either waiting inside connect() or sleeping between attempts).
58-
// This is intentionally orthogonal to state_: a SocketState::Closed left over
59-
// from a deliberate close()+connect() reconnect must NOT abort a fresh
60-
// attempt, whereas requestStop() (used at teardown) must abort reliably and
61-
// cannot be clobbered by setup()'s internal state_ resets.
62-
std::atomic<bool> stop_requested_{ false };
63-
6460
void setupOptions();
6561

62+
// True while a deliberate disconnect() is in progress or has completed (the "deliberate-stop
63+
// set"). The connect/retry machinery checks this to abort, and never overwrites these states,
64+
// so a teardown disconnect() that races a reconnect attempt is observed reliably.
65+
bool isStopRequested() const
66+
{
67+
const SocketState s = state_.load();
68+
return s == SocketState::Disconnecting || s == SocketState::Disconnected;
69+
}
70+
71+
// Atomically moves state_ to `desired`, unless a deliberate disconnect() (Disconnecting or
72+
// Disconnected) is in effect. Returns true if the state was set, false if a deliberate stop is
73+
// active (in which case state_ is left untouched). This is how the connect/retry machinery
74+
// updates its in-progress state without ever clobbering a teardown signal.
75+
bool setStateUnlessStopRequested(SocketState desired);
76+
6677
// Performs an interruptible, non-blocking connect on an already-created socket.
67-
// Polls in short slices so that a concurrent requestStop() aborts the attempt
78+
// Polls in short slices so that a concurrent disconnect() aborts the attempt
6879
// promptly on all platforms (POSIX close() of a blocked connect() is reliable,
6980
// Winsock's is not). Restores blocking mode on success.
7081
bool openInterruptible(socket_t socket_fd, struct sockaddr* address, size_t address_len);
@@ -78,6 +89,18 @@ class TCPSocket
7889
bool setup(const std::string& host, const int port, const size_t max_num_tries = 0,
7990
const std::chrono::milliseconds reconnection_time = DEFAULT_RECONNECTION_TIME);
8091

92+
/*!
93+
* \brief Re-establishes a connection after an unexpected drop, without clearing a deliberate
94+
* disconnect().
95+
*
96+
* Used by the automatic reconnect path (e.g. the producer loop). If a deliberate disconnect()
97+
* is in progress or has completed, this returns false immediately instead of reconnecting, so a
98+
* concurrent teardown is never undone. Otherwise behaves like setup() but marks the socket as
99+
* Reconnecting while the attempt is in progress.
100+
*/
101+
bool reconnect(const std::string& host, const int port, const size_t max_num_tries = 0,
102+
const std::chrono::milliseconds reconnection_time = DEFAULT_RECONNECTION_TIME);
103+
81104
std::unique_ptr<timeval> recv_timeout_;
82105

83106
public:
@@ -147,29 +170,42 @@ class TCPSocket
147170
bool write(const uint8_t* buf, const size_t buf_len, size_t& written);
148171

149172
/*!
150-
* \brief Closes the connection to the socket.
173+
* \brief Establishes a connection to the configured host/port.
174+
*
175+
* This is the explicit (re)connect entry point. It clears any prior deliberate disconnect()
176+
* (moving the socket to Connecting) and then attempts to connect, retrying up to max_num_tries
177+
* times (unlimited when 0). Call this on the controlling thread; the automatic reconnect path
178+
* uses the internal reconnect() instead.
179+
*
180+
* \param host Host to connect to
181+
* \param port Port to connect to
182+
* \param max_num_tries Maximum number of connection attempts before failing. Unlimited when 0.
183+
* \param reconnection_time Time between connection attempts
184+
*
185+
* \returns True on success, false if the connection could not be established or was aborted by
186+
* a concurrent disconnect()
151187
*/
152-
void close();
188+
bool connect(const std::string& host, const int port, const size_t max_num_tries = 0,
189+
const std::chrono::milliseconds reconnection_time = DEFAULT_RECONNECTION_TIME);
153190

154191
/*!
155-
* \brief Requests that any in-progress (or future) connection attempt be aborted.
192+
* \brief Deliberately disconnects the socket and leaves it ready to connect() again.
156193
*
157-
* Sets a sticky cancellation flag and closes the socket. A reconnect thread that is
158-
* waiting inside setup() (either in connect() or in the between-attempt back-off) returns
159-
* promptly instead of blocking until the reconnection timeout expires. Use this at teardown
160-
* (e.g. from a destructor) before joining a reconnect thread. The flag stays set until
161-
* clearStop() is called, so it cannot be lost by a racing internal state reset.
194+
* Moves the socket into the deliberate-stop set (Disconnecting then Disconnected) and closes the
195+
* underlying file descriptor. Any connect/reconnect attempt currently in progress (blocked in a
196+
* connect or sleeping between attempts) aborts promptly, and the automatic reconnect path will
197+
* not reconnect until connect() is called again. Use this at teardown (e.g. from a destructor)
198+
* before joining a reconnect thread.
162199
*/
163-
void requestStop();
200+
void disconnect();
164201

165202
/*!
166-
* \brief Clears the cancellation flag set by requestStop().
203+
* \brief Closes the connection to the socket.
167204
*
168-
* Must be called on a controlled (re)start before attempting to connect again, so a socket
169-
* that was previously stopped can be reused. Never call this concurrently with a reconnect
170-
* thread.
205+
* Neutral low-level close. Unlike disconnect(), it does not prevent a subsequent automatic
206+
* reconnect, and it never downgrades a deliberate disconnect() that is already in effect.
171207
*/
172-
void clearStop();
208+
void close();
173209

174210
/*!
175211
* \brief Setup Receive timeout used for this socket.

0 commit comments

Comments
 (0)