Compare commits

...

4 Commits

Author SHA1 Message Date
73d098872c Do not warn on terminating the server
If the server is already dead, terminating it fails. This is expected.
2020-03-29 11:24:11 +02:00
943d264b35 Do not block on accept() if server died
The server may die before connecting to the client. In that case, the
client was blocked indefinitely (until Ctrl+C) on accept().

To avoid the problem, use a pipe and a select() before calling accept(),
so that the blocking call can be interrupted. Once accept() is called,
it is guaranteed not to block.
2020-03-29 11:24:11 +02:00
32dc192883 Wait server from a separate thread
Create a thread just to wait for the server process exit.

This paves the way to simply wake up a blocking accept() in a portable
way.
2020-03-28 23:56:37 +01:00
7732f1097b Refactor server_start() error handling
This will avoid more cleanup duplication.
2020-03-28 23:56:37 +01:00
4 changed files with 114 additions and 18 deletions

View File

@ -5,6 +5,8 @@
#include <inttypes.h>
#include <libgen.h>
#include <stdio.h>
#include <unistd.h>
#include <SDL2/SDL_thread.h>
#include <SDL2/SDL_timer.h>
#include <SDL2/SDL_platform.h>
@ -332,6 +334,18 @@ server_init(struct server *server) {
*server = (struct server) SERVER_INITIALIZER;
}
static int
run_wait_server(void *data) {
struct server *server = data;
cmd_simple_wait(server->process, NULL); // ignore exit code
// wake up any net_select_interruptible()
close(server->pipe_intr[1]);
LOGD("Server terminated");
return 0;
}
bool
server_start(struct server *server, const char *serial,
const struct server_params *params) {
@ -345,46 +359,79 @@ server_start(struct server *server, const char *serial,
}
if (!push_server(serial)) {
SDL_free(server->serial);
return false;
goto error1;
}
if (!enable_tunnel_any_port(server, params->port_range)) {
SDL_free(server->serial);
return false;
goto error1;
}
bool ok = net_pipe(server->pipe_intr);
if (!ok) {
perror("pipe");
goto error2;
}
// server will connect to our server socket
server->process = execute_server(server, params);
if (server->process == PROCESS_NONE) {
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
}
disable_tunnel(server);
SDL_free(server->serial);
return false;
goto error3;
}
server->wait_server_thread =
SDL_CreateThread(run_wait_server, "wait-server", server);
if (!server->wait_server_thread) {
cmd_terminate(server->process);
cmd_simple_wait(server->process, NULL); // ignore exit code
goto error3;
}
server->tunnel_enabled = true;
return true;
error3:
close(server->pipe_intr[0]);
close(server->pipe_intr[1]);
error2:
if (!server->tunnel_forward) {
close_socket(&server->server_socket);
}
disable_tunnel(server);
error1:
SDL_free(server->serial);
return false;
}
bool
server_connect_to(struct server *server) {
if (!server->tunnel_forward) {
bool acceptable = net_select_interruptible(server->server_socket,
server->pipe_intr[0]);
if (!acceptable) {
// the process died, accept() would never succeed
return false;
}
server->video_socket = net_accept(server->server_socket);
if (server->video_socket == INVALID_SOCKET) {
return false;
}
acceptable = net_select_interruptible(server->server_socket,
server->pipe_intr[0]);
if (!acceptable) {
// the process died, accept() would never succeed
return false;
}
server->control_socket = net_accept(server->server_socket);
if (server->control_socket == INVALID_SOCKET) {
// the video_socket will be cleaned up on destroy
return false;
}
close(server->pipe_intr[0]);
// we don't need the server socket anymore
close_socket(&server->server_socket);
} else {
@ -425,17 +472,14 @@ server_stop(struct server *server) {
assert(server->process != PROCESS_NONE);
if (!cmd_terminate(server->process)) {
LOGW("Could not terminate server");
}
cmd_simple_wait(server->process, NULL); // ignore exit code
LOGD("Server terminated");
cmd_terminate(server->process);
if (server->tunnel_enabled) {
// ignore failure
disable_tunnel(server);
}
SDL_WaitThread(server->wait_server_thread, NULL);
}
void

View File

@ -3,6 +3,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_thread.h>
#include "config.h"
#include "command.h"
@ -12,6 +13,8 @@
struct server {
char *serial;
process_t process;
SDL_Thread *wait_server_thread;
int pipe_intr[2]; // to wake up blocking accept() on process exit
socket_t server_socket; // only used if !tunnel_forward
socket_t video_socket;
socket_t control_socket;

View File

@ -1,16 +1,22 @@
#include "net.h"
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <SDL2/SDL_platform.h>
#include "config.h"
#include "common.h"
#include "log.h"
#ifdef __WINDOWS__
# include <io.h>
# include <winsock2.h>
typedef int socklen_t;
#else
# include <sys/types.h>
# include <sys/select.h>
# include <sys/socket.h>
# include <sys/types.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <unistd.h>
@ -145,3 +151,38 @@ net_close(socket_t socket) {
return !close(socket);
#endif
}
bool
net_select_interruptible(int fd, int fd_intr) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
FD_SET(fd_intr, &rfds);
int nfds = MAX(fd, fd_intr) + 1;
// use select() because it's available on supported platforms
int r = select(nfds, &rfds, NULL, NULL, NULL);
if (r == -1) {
// failure
return false;
}
assert(r > 0);
if (FD_ISSET(fd_intr, &rfds)) {
// interrupted is set
return false;
}
assert(FD_ISSET(fd, &rfds));
return true;
}
bool
net_pipe(int fds[static 2]) {
#ifdef __WINDOWS__
return !_pipe(fds, 4096, 0);
#else
return !pipe(fds);
#endif
}

View File

@ -54,4 +54,12 @@ net_shutdown(socket_t socket, int how);
bool
net_close(socket_t socket);
// wait for fd or fd_intr to be readable
// return true if fd is readable and fd_intr is not
bool
net_select_interruptible(int fd, int fd_intr);
bool
net_pipe(int fd[static 2]);
#endif