Skip to content

Commit 336fc84

Browse files
committed
libvncserver: indicate client thread end by other means than socket
Before, the client-to-server thread would end when the client's socket was set to RFB_INVALID_SOCKET. However, there's a race condition where the client's socket can be set to invalid while the client-to-server thread is in select(), causing bk138/droidVNC-NG#8. Fix this by: * introducing an additional RFB_SHUTDOWN state for clients * use this as an end condition in the client-to-server thread instead of the socket * move the self-pipe notification to rfbCloseClient from rfbShutdownServer.
1 parent 0ab7a73 commit 336fc84

3 files changed

Lines changed: 52 additions & 23 deletions

File tree

libvncserver/main.c

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ clientOutput(void *data)
461461
while (1) {
462462
haveUpdate = false;
463463
while (!haveUpdate) {
464-
if (cl->sock == RFB_INVALID_SOCKET) {
464+
if (cl->sock == RFB_INVALID_SOCKET || cl->state == RFB_SHUTDOWN) {
465465
/* Client has disconnected. */
466466
return THREAD_ROUTINE_RETURN_VALUE;
467467
}
@@ -528,7 +528,7 @@ clientInput(void *data)
528528
uintptr_t output_thread = _beginthread(clientOutput, 0, cl);
529529
#endif
530530

531-
while (1) {
531+
while (cl->state != RFB_SHUTDOWN) {
532532
fd_set rfds, wfds, efds;
533533
struct timeval tv;
534534
int n;
@@ -604,6 +604,10 @@ clientInput(void *data)
604604
UNLOCK(cl->updateMutex);
605605
THREAD_JOIN(output_thread);
606606

607+
/* Close client sock */
608+
rfbCloseSocket(cl->sock);
609+
cl->sock = RFB_INVALID_SOCKET;
610+
607611
rfbClientConnectionGone(cl);
608612

609613
return THREAD_ROUTINE_RETURN_VALUE;
@@ -620,8 +624,16 @@ listenerRun(void *data)
620624
socklen_t len;
621625
fd_set listen_fds; /* temp file descriptor list for select() */
622626

627+
/*
628+
Only checking socket state here and not using rfbIsActive()
629+
because: When rfbShutdownServer() is called by the client, it runs in
630+
the client-to-server thread's context, resulting in itself calling
631+
its own the pthread_join(), returning immediately, leaving the
632+
client-to-server thread to actually terminate _after_ the listener thread
633+
is terminated, leaving the client list still populated.
634+
*/
623635
/* TODO: HTTP is not handled */
624-
while (rfbIsActive(screen)) {
636+
while (screen->socketState != RFB_SOCKET_SHUTDOWN) {
625637
client_fd = -1;
626638
FD_ZERO(&listen_fds);
627639
if(screen->listenSock != RFB_INVALID_SOCKET)
@@ -1173,22 +1185,14 @@ void rfbShutdownServer(rfbScreenInfoPtr screen,rfbBool disconnectClients) {
11731185
rfbCloseClient(currentCl);
11741186
}
11751187

1176-
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
1177-
if(currentCl->screen->backgroundLoop) {
11781188
/*
1179-
Notify the thread. This simply writes a NULL byte to the notify pipe in order to get past the select()
1180-
in clientInput(), the loop in there will then break because the rfbCloseClient() above has set
1181-
currentCl->sock to -1.
1182-
*/
1183-
write(currentCl->pipe_notify_client_thread[1], "\x00", 1);
1184-
/* And wait for it to finish. */
1185-
pthread_join(currentCl->client_thread, NULL);
1186-
} else {
1187-
rfbClientConnectionGone(currentCl);
1188-
}
1189-
#else
1190-
rfbClientConnectionGone(currentCl);
1189+
In threaded mode, rfbClientConnectionGone() is called by the client-to-server thread.
1190+
Only need to call this here for non-threaded mode.
1191+
*/
1192+
#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) || defined(LIBVNCSERVER_HAVE_WIN32THREADS)
1193+
if(!currentCl->screen->backgroundLoop)
11911194
#endif
1195+
rfbClientConnectionGone(currentCl);
11921196

11931197
currentCl = nextCl;
11941198
}
@@ -1200,6 +1204,7 @@ void rfbShutdownServer(rfbScreenInfoPtr screen,rfbBool disconnectClients) {
12001204
rfbShutdownSockets(screen);
12011205

12021206
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
1207+
screen->socketState = RFB_SOCKET_SHUTDOWN; /* Set this so the listener thread ends */
12031208
if (screen->backgroundLoop) {
12041209
/*
12051210
Notify the listener thread. This simply writes a NULL byte to the notify pipe in order to get past the select()

libvncserver/sockets.c

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -556,24 +556,47 @@ rfbCloseClient(rfbClientPtr cl)
556556
if (cl->sock != RFB_INVALID_SOCKET)
557557
#endif
558558
{
559+
/* Remove client sock from allFds and adapt maxFd */
559560
FD_CLR(cl->sock,&(cl->screen->allFds));
560561
if(cl->sock==cl->screen->maxFd)
561562
while(cl->screen->maxFd>0
562563
&& !FD_ISSET(cl->screen->maxFd,&(cl->screen->allFds)))
563564
cl->screen->maxFd--;
564565
#ifdef LIBVNCSERVER_WITH_WEBSOCKETS
566+
/* Has to happen before socket close as the SSL implementation might send a goodbye */
565567
if (cl->sslctx)
566568
rfbssl_destroy(cl);
567569
free(cl->wspath);
568570
#endif
569-
#ifndef __MINGW32__
570-
shutdown(cl->sock,SHUT_RDWR);
571-
#endif
572-
rfbCloseSocket(cl->sock);
573-
cl->sock = RFB_INVALID_SOCKET;
574571
}
575572
TSIGNAL(cl->updateCond);
576573
UNLOCK(cl->updateMutex);
574+
575+
/*
576+
Client socket closing, either via the client-to-server thread or directly.
577+
*/
578+
#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) || defined(LIBVNCSERVER_HAVE_WIN32THREADS)
579+
if(cl->screen->backgroundLoop) {
580+
/* Indicate to client-to-server thread that it should not go on */
581+
cl->state = RFB_SHUTDOWN;
582+
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
583+
/*
584+
Notify the thread. This simply writes a NULL byte to the notify pipe in order to get past the select()
585+
in clientInput(), the loop in there will then break because the client state has been set to
586+
RFB_SHUTDOWN. Client socket closing will be done by the thread.
587+
*/
588+
write(cl->pipe_notify_client_thread[1], "\x00", 1);
589+
/* And wait for it to finish. */
590+
pthread_join(cl->client_thread, NULL);
591+
#endif
592+
} else
593+
#endif
594+
/* Either no threading support or threading support with screen->backgroundloop == false */
595+
{
596+
/* Close client sock */
597+
rfbCloseSocket(cl->sock);
598+
cl->sock = RFB_INVALID_SOCKET;
599+
}
577600
}
578601

579602

rfb/rfb.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,8 @@ typedef struct _rfbClientRec {
491491
/* Ephemeral internal-use states that will never be seen by software
492492
* using LibVNCServer to provide services: */
493493

494-
RFB_INITIALISATION_SHARED /**< sending initialisation messages with implicit shared-flag already true */
494+
RFB_INITIALISATION_SHARED, /**< sending initialisation messages with implicit shared-flag already true */
495+
RFB_SHUTDOWN /**< shutting down */
495496
} state;
496497

497498
rfbBool reverseConnection;

0 commit comments

Comments
 (0)