Skip to content

Commit 67b97d3

Browse files
committed
Make TCPSocket send all data when blocking
Previously, send() was somewhat soft - it only ever made one send call to the underlying stack, so it would typically take as much data as would fit in the buffer, and only block if it was unable to write anything. This is not the intent of a POSIX socket/filehandle write. It should try to send everything if blocking, and only send less if interrupted by a signal: - If the O_NONBLOCK flag is clear, write() shall block the calling thread until the data can be accepted. - If the O_NONBLOCK flag is set, write() shall not block the thread. If some data can be written without blocking the thread, write() shall write what it can and return the number of bytes written. Otherwise, it shall return -1 and set errno to [EAGAIN]. This "send all" behaviour is of slightly limited usefulness in POSIX, as you still usually have to worry about the interruption possibility: - If write() is interrupted by a signal before it writes any data, it shall return -1 with errno set to [EINTR]. - If write() is interrupted by a signal after it successfully writes some data, it shall return the number of bytes written. But as mbed OS does not have the possibility of signal interruption, if we strengthen send to write everything, we can make applications' lives easier - they can just do "send(large amount)" confident that it will all go in one call (if no errors). So, rework to make multiple sends to the underlying stack, blocking as necessary, until all data is written. This change does not apply to recv(), which is correct in only blocking until some data is available: - If O_NONBLOCK is set, read() shall return -1 and set errno to [EAGAIN]. - If O_NONBLOCK is clear, read() shall block the calling thread until some data becomes available. - The use of the O_NONBLOCK flag has no effect if there is some data available.
1 parent a519b84 commit 67b97d3

File tree

2 files changed

+29
-11
lines changed

2 files changed

+29
-11
lines changed

features/netsocket/TCPSocket.cpp

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,25 +105,36 @@ nsapi_error_t TCPSocket::connect(const char *host, uint16_t port)
105105
nsapi_size_or_error_t TCPSocket::send(const void *data, nsapi_size_t size)
106106
{
107107
_lock.lock();
108+
const uint8_t *data_ptr = static_cast<const uint8_t *>(data);
108109
nsapi_size_or_error_t ret;
110+
nsapi_size_t written = 0;
109111

110112
// If this assert is hit then there are two threads
111113
// performing a send at the same time which is undefined
112114
// behavior
113115
MBED_ASSERT(!_write_in_progress);
114116
_write_in_progress = true;
115117

118+
// Unlike recv, we should write the whole thing if blocking. POSIX only
119+
// allows partial as a side-effect of signal handling; it normally tries to
120+
// write everything if blocking. Without signals we can always write all.
116121
while (true) {
117122
if (!_socket) {
118123
ret = NSAPI_ERROR_NO_SOCKET;
119124
break;
120125
}
121126

122127
_pending = 0;
123-
ret = _stack->socket_send(_socket, data, size);
124-
if ((_timeout == 0) || (ret != NSAPI_ERROR_WOULD_BLOCK)) {
128+
ret = _stack->socket_send(_socket, data_ptr + written, size - written);
129+
if (ret >= 0) {
130+
written += ret;
131+
if (written >= size) {
132+
break;
133+
}
134+
}
135+
if (_timeout == 0) {
125136
break;
126-
} else {
137+
} else if (ret == NSAPI_ERROR_WOULD_BLOCK) {
127138
uint32_t flag;
128139

129140
// Release lock before blocking so other threads
@@ -134,15 +145,22 @@ nsapi_size_or_error_t TCPSocket::send(const void *data, nsapi_size_t size)
134145

135146
if (flag & osFlagsError) {
136147
// Timeout break
137-
ret = NSAPI_ERROR_WOULD_BLOCK;
138148
break;
139149
}
150+
} else if (ret < 0) {
151+
break;
140152
}
141153
}
142154

143155
_write_in_progress = false;
144156
_lock.unlock();
145-
return ret;
157+
if (ret <= 0 && ret != NSAPI_ERROR_WOULD_BLOCK) {
158+
return ret;
159+
} else if (written == 0) {
160+
return NSAPI_ERROR_WOULD_BLOCK;
161+
} else {
162+
return written;
163+
}
146164
}
147165

148166
nsapi_size_or_error_t TCPSocket::recv(void *data, nsapi_size_t size)

features/netsocket/TCPSocket.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ class TCPSocket : public Socket {
8888
* The socket must be connected to a remote host. Returns the number of
8989
* bytes sent from the buffer.
9090
*
91-
* By default, send blocks until data is sent. If socket is set to
92-
* non-blocking or times out, NSAPI_ERROR_WOULD_BLOCK is returned
93-
* immediately.
91+
* By default, send blocks until all data is sent. If socket is set to
92+
* non-blocking or times out, a partial amount can be written.
93+
* NSAPI_ERROR_WOULD_BLOCK is returned if no data was written.
9494
*
9595
* @param data Buffer of data to send to the host
9696
* @param size Size of the buffer in bytes
@@ -104,9 +104,9 @@ class TCPSocket : public Socket {
104104
* The socket must be connected to a remote host. Returns the number of
105105
* bytes received into the buffer.
106106
*
107-
* By default, recv blocks until data is sent. If socket is set to
108-
* non-blocking or times out, NSAPI_ERROR_WOULD_BLOCK is returned
109-
* immediately.
107+
* By default, recv blocks until some data is received. If socket is set to
108+
* non-blocking or times out, NSAPI_ERROR_WOULD_BLOCK can be returned to
109+
* indicate no data.
110110
*
111111
* @param data Destination buffer for data received from the host
112112
* @param size Size of the buffer in bytes

0 commit comments

Comments
 (0)