Skip to content

Commit 088e876

Browse files
committed
libvncserver: indicate client thread end by other means than sock v2
This is a less intrusive version of 336fc84 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, but only pthread_join() in rfbShutdownServer
1 parent b9bc09b commit 088e876

3 files changed

Lines changed: 53 additions & 16 deletions

File tree

libvncserver/main.c

Lines changed: 22 additions & 10 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,18 @@ 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, making rfbIsActive()
634+
return true, not ending the listener, making the join in rfbShutdownServer()
635+
wait forever...
636+
*/
623637
/* TODO: HTTP is not handled */
624-
while (rfbIsActive(screen)) {
638+
while (screen->socketState != RFB_SOCKET_SHUTDOWN) {
625639
client_fd = -1;
626640
FD_ZERO(&listen_fds);
627641
if(screen->listenSock != RFB_INVALID_SOCKET)
@@ -1175,15 +1189,13 @@ void rfbShutdownServer(rfbScreenInfoPtr screen,rfbBool disconnectClients) {
11751189

11761190
#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
11771191
if(currentCl->screen->backgroundLoop) {
1178-
/*
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. */
1192+
/* Wait for threads to finish. The thread has already been pipe-notified by rfbCloseClient() */
11851193
pthread_join(currentCl->client_thread, NULL);
11861194
} else {
1195+
/*
1196+
In threaded mode, rfbClientConnectionGone() is called by the client-to-server thread.
1197+
Only need to call this here for non-threaded mode.
1198+
*/
11871199
rfbClientConnectionGone(currentCl);
11881200
}
11891201
#else

libvncserver/sockets.c

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -556,24 +556,48 @@ 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+
/*
590+
No joining of threads here, this is fire and forget.
591+
*/
592+
#endif
593+
} else
594+
#endif
595+
/* Either no threading support or threading support with screen->backgroundloop == false */
596+
{
597+
/* Close client sock */
598+
rfbCloseSocket(cl->sock);
599+
cl->sock = RFB_INVALID_SOCKET;
600+
}
577601
}
578602

579603

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 /**< Client is shutting down */
495496
} state;
496497

497498
rfbBool reverseConnection;

0 commit comments

Comments
 (0)