|
2 | 2 |
|
3 | 3 | #include <errno.h>
|
4 | 4 | #ifndef _WIN32
|
| 5 | +#include <sys/types.h> |
5 | 6 | #include <sys/socket.h>
|
6 | 7 | #endif
|
7 | 8 | #include <unistd.h>
|
@@ -162,24 +163,58 @@ static void *nogvl_connect(void *ptr) {
|
162 | 163 | return (void *)(client ? Qtrue : Qfalse);
|
163 | 164 | }
|
164 | 165 |
|
| 166 | +#ifndef _WIN32 |
| 167 | +/* |
| 168 | + * Redirect clientfd to a dummy socket for mysql_close to |
| 169 | + * write, shutdown, and close on as a no-op. |
| 170 | + * We do this hack because we want to call mysql_close to release |
| 171 | + * memory, but do not want mysql_close to drop connections in the |
| 172 | + * parent if the socket got shared in fork. |
| 173 | + * Returns Qtrue or false (success or failure) |
| 174 | + */ |
| 175 | +static VALUE invalidate_fd(int clientfd) |
| 176 | +{ |
| 177 | + /* TODO: set cloexec flags, atomically if possible */ |
| 178 | + int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); |
| 179 | + |
| 180 | + if (sockfd < 0) { |
| 181 | + /* |
| 182 | + * Cannot raise here, because one or both of the following may be true: |
| 183 | + * a) we have no GVL (in C Ruby) |
| 184 | + * b) are running as a GC finalizer |
| 185 | + */ |
| 186 | + return Qfalse; |
| 187 | + } |
| 188 | + |
| 189 | + dup2(sockfd, clientfd); |
| 190 | + close(sockfd); |
| 191 | + |
| 192 | + return Qtrue; |
| 193 | +} |
| 194 | +#endif /* _WIN32 */ |
| 195 | + |
165 | 196 | static void *nogvl_close(void *ptr) {
|
166 | 197 | mysql_client_wrapper *wrapper;
|
167 | 198 | wrapper = ptr;
|
168 | 199 | if (wrapper->connected) {
|
169 | 200 | wrapper->active_thread = Qnil;
|
170 | 201 | wrapper->connected = 0;
|
171 | 202 | #ifndef _WIN32
|
172 |
| - /* Call close() on the socket before calling mysql_close(). This prevents |
| 203 | + /* Invalidate the socket before calling mysql_close(). This prevents |
173 | 204 | * mysql_close() from sending a mysql-QUIT or from calling shutdown() on
|
174 |
| - * the socket. The difference is that close() will drop this process's |
175 |
| - * reference to the socket only, while a QUIT or shutdown() would render |
176 |
| - * the underlying connection unusable, interrupting other processes which |
177 |
| - * share this object across a fork(). |
| 205 | + * the socket. The difference is that invalidate_fd will drop this |
| 206 | + * process's reference to the socket only, while a QUIT or shutdown() |
| 207 | + * would render the underlying connection unusable, interrupting other |
| 208 | + * processes which share this object across a fork(). |
178 | 209 | */
|
179 |
| - close(wrapper->client->net.fd); |
| 210 | + if (invalidate_fd(wrapper->client->net.fd) == Qfalse) { |
| 211 | + fprintf(stderr, "[WARN] mysql2 failed to invalidate FD safely, leaking some memory\n"); |
| 212 | + close(wrapper->client->net.fd); |
| 213 | + return NULL; |
| 214 | + } |
180 | 215 | #endif
|
181 | 216 |
|
182 |
| - mysql_close(wrapper->client); |
| 217 | + mysql_close(wrapper->client); /* only used to free memory at this point */ |
183 | 218 | }
|
184 | 219 |
|
185 | 220 | return NULL;
|
|
0 commit comments