Re: [PATCH v2] close.2: Suggest shutdown(2) when closing socket from another thread

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 2023-09-14 11:38, Alejandro Colomar wrote:
> On 2023-09-14 09:42, Tomáš Golembiovský wrote:
>> This is another take on the ancient saga of closing sockets from one
>> thread while another thread is blocked on recv(2). It all started with
>> [1,2] and continued by [3]. It was established that this is expected and
>> long term behavior or Linux and the issue was closed by Michael Kerrisk
>> by commit c2f15a1349a7271f6c1d81361ec8b256266e1c09.
>>
>> This is all fine and the patch covered the issue in general terms.
>> However, it does not mention the specific case of sockets and shutdown,
>> where the issue can be (at least for the read case) mitigated by proper
>> shutdown. While one may argue that such information may be implied by
>> other man pages (perhaps return value of recv(2)) and thus is redundant,
>> it seems only fair to mention shutdown(2) here as it is only rarely
>> noted in Linux documentation that properly shutting down both side of
>> the socket is a good programming practice when dealing with sockets.
>>
>> As a test program I am attaching the program originally written by Lukas
>> Czerner. Only with small fixes here and there.
>>
>> [1] https://lore.kernel.org/linux-man/1314620800-15587-1-git-send-email-lczerner@xxxxxxxxxx/
>> [2] https://bugzilla.redhat.com/show_bug.cgi?id=650985
>> [3] https://bugzilla.kernel.org/show_bug.cgi?id=53781
>>
>> ```c
>> /**
>>  * Copyright Red Hat
>>  * SPDX-License-Identifier: GPL-2.0-or-later
>>  */
>>
>> void *close_socket(void *arg) {
>>         int sockfd = *(int *)arg;
>>
>>         sleep(3);
>>         printf("Thread: closing socket %d\n", sockfd);
>> //      shutdown(sockfd, SHUT_RDWR);
>>         close(sockfd);
>>         return NULL;
>> }
>>
>> int client(void) {
>>         int sockfd;
>>         int len;
>>         struct sockaddr_un address;
>>         int ret;
>>         char *buffer=malloc(BUFSIZE);
>>         pthread_t thread;
>>
>>         sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
>>
>>         address.sun_family = AF_UNIX;
>>         strcpy(address.sun_path, "server_socket");
>>         len = sizeof(address);
>>
>>         ret = connect(sockfd, (struct sockaddr *)&address, len);
>>         if (ret == -1) {
>>                 perror("connect");
>>                 return 1;
>>         }
>>         printf("client connected\n");
>>
>>         ret = pthread_create(&thread, NULL, close_socket, (void *)&sockfd);
>>         if (ret != 0) {
>>                 perror("Creating thread failed");
>>                 return 1;
>>         }
>>
>>         while (1) {
>>                 ret = recv(sockfd,buffer,BUFSIZE,0);
>>                 if (ret < 0) {
>>                         perror("recv");
>>                         return 1;
>>                 }
>>                 printf("Data received: %s\n", buffer);
>>                 sleep(1);
>>         }
>>
>>         close(sockfd);
>>         return 0;
>> }
>>
>> int server(void) {
>>         char *message="This is the message I am sending to you";
>>         struct sockaddr_un server_addr, client_addr;
>>         int server_sockfd, client_sockfd;
>>         socklen_t server_len, client_len;
>>         int ret;
>>
>>         unlink("server_socket");
>>         server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
>>
>>         server_addr.sun_family = AF_UNIX;
>>         strcpy(server_addr.sun_path, "server_socket");
>>         server_len = sizeof(server_addr);
>>         bind(server_sockfd, (struct sockaddr *)&server_addr, server_len);
>>
>>         listen(server_sockfd, 5);
>>
>>         client_len = sizeof(client_addr);
>>         client_sockfd = accept(server_sockfd,
>>                 (struct sockaddr *)&client_addr, &client_len);
>>
>>         printf("Server: sending data...\n");
>>         ret = send(client_sockfd ,message,strlen(message),0);
>>         if (ret < 0) {
>>                 perror("send");
>>                 return 1;
>>         }
>>
>>         /* simulate running server by not closing the client_socket socket */
>>         return 0;
>> }
>>
>> int main() {
>>         pid_t pid;
>>
>>         pid = fork();
>>         if (pid < 0) {
>>                 perror("fork failed");
>>                 exit(1);
>>         }
>>         if (pid > 0) {
>>                 printf(" - starting server\n");
>>                 server();
>>                 printf(" - exiting server\n");
>>                 wait(NULL);
>>         } else {
>>                 sleep(1);
>>                 printf(" - starting client\n");
>>                 client();
>>                 printf(" - exiting client\n");
>>         }
>> }
>> ```
> 
> Here are some fixes to the program:
> 
> $ diff -u old.c new.c 
> --- old.c	2023-09-14 11:35:06.556184223 +0200
> +++ new.c	2023-09-14 11:35:33.109203789 +0200
> @@ -3,6 +3,16 @@
>   * SPDX-License-Identifier: GPL-2.0-or-later
>   */
>  
> + #include <pthread.h>
> + #include <stddef.h>
> + #include <stdio.h>
> + #include <stdlib.h>
> + #include <string.h>
> + #include <sys/socket.h>
> + #include <sys/un.h>
> + #include <sys/wait.h>
> + #include <unistd.h>
> +
>  void *close_socket(void *arg) {
>          int sockfd = *(int *)arg;
>  
> @@ -15,10 +25,11 @@
>  
>  int client(void) {
>          int sockfd;
> -        int len;
> +        socklen_t len;
>          struct sockaddr_un address;
>          int ret;
> -        char *buffer=malloc(BUFSIZE);
> +        ssize_t r;
> +        char *buffer=malloc(BUFSIZ);
>          pthread_t thread;
>  
>          sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
> @@ -41,8 +52,8 @@
>          }
>  
>          while (1) {
> -                ret = recv(sockfd,buffer,BUFSIZE,0);
> -                if (ret < 0) {
> +                r = recv(sockfd, buffer, BUFSIZ, 0);
> +                if (r < 0) {
>                          perror("recv");
>                          return 1;
>                  }
> @@ -59,7 +70,7 @@
>          struct sockaddr_un server_addr, client_addr;
>          int server_sockfd, client_sockfd;
>          socklen_t server_len, client_len;
> -        int ret;
> +        ssize_t ret;
>  
>          unlink("server_socket");
>          server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
> @@ -76,7 +87,7 @@
>                  (struct sockaddr *)&client_addr, &client_len);
>  
>          printf("Server: sending data...\n");
> -        ret = send(client_sockfd ,message,strlen(message),0);
> +        ret = send(client_sockfd, message, strlen(message), 0);
>          if (ret < 0) {
>                  perror("send");
>                  return 1;
> 
> 
> After those fixes, I still get a warning of dead code:
> 
> $ clang -Weverything -Wno-missing-prototypes new.c
> new.c:64:9: warning: code will never be executed [-Wunreachable-code]
>         close(sockfd);
>         ^~~~~
> 1 warning generated.

And one from clang-tidy(1):

/home/alx/tmp/new.c:81:9: error: the value returned by this function should be used [bugprone-unused-return-value,-warnings-as-errors]
        bind(server_sockfd, (struct sockaddr *)&server_addr, server_len);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It seems it didn't realize about the ignored return value of listen(2),
but it seems there are a few functions whose error shouldn't be ignored.


And a couple from cppcheck(1):

$ cppcheck --enable=all --error-exitcode=2 --inconclusive --quiet \
           --suppressions-list=./etc/cppcheck/cppcheck.suppress \
           /home/alx/tmp/new.c;
/home/alx/tmp/new.c:44:17: error: Memory leak: buffer [memleak]
                return 1;
                ^
/home/alx/tmp/new.c:51:17: error: Memory leak: buffer [memleak]
                return 1;
                ^

Cheers,
Alex


> 
>>
>> Signed-off-by: Tomáš Golembiovský <tgolembi@xxxxxxxxxx>
>> ---
>>
>> v2: updated test file header
>>
>>  man2/close.2 | 5 +++++
>>  1 file changed, 5 insertions(+)
>>
>> diff --git a/man2/close.2 b/man2/close.2
>> index 68211bc58..08c6a0839 100644
>> --- a/man2/close.2
>> +++ b/man2/close.2
>> @@ -145,6 +145,11 @@ Thus, the blocking system call in the first thread may successfully
>>  complete after the
>>  .BR close ()
>>  in the second thread.
>> +.PP
>> +When dealing with sockets,
>> +blocking forever in another thread may be prevented by using
>> +.BR shutdown (2)
>> +to shut down both parts of the connection before closing the socket.
>>  .\"
>>  .SS Dealing with error returns from close()
>>  A careful programmer will check the return value of
> 

-- 
<http://www.alejandro-colomar.es/>
GPG key fingerprint: A9348594CE31283A826FBDD8D57633D441E25BB5

Attachment: OpenPGP_signature.asc
Description: OpenPGP digital signature


[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux