From: Alejandro Colomar <alx@kernel.org>
To: "Tomáš Golembiovský" <tgolembi@redhat.com>
Cc: linux-man@vger.kernel.org
Subject: Re: [PATCH v2] close.2: Suggest shutdown(2) when closing socket from another thread
Date: Thu, 14 Sep 2023 11:49:43 +0200 [thread overview]
Message-ID: <6e35753c-a96b-4632-bfda-d1fae345974d@kernel.org> (raw)
In-Reply-To: <612c83fe-2dcb-4029-adf7-f37a59cce2a7@kernel.org>
[-- Attachment #1.1: Type: text/plain, Size: 8808 bytes --]
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@redhat.com/
>> [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@redhat.com>
>> ---
>>
>> 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 #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
prev parent reply other threads:[~2023-09-14 9:49 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-09-14 7:42 [PATCH v2] close.2: Suggest shutdown(2) when closing socket from another thread Tomáš Golembiovský
2023-09-14 9:38 ` Alejandro Colomar
2023-09-14 9:49 ` Alejandro Colomar [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=6e35753c-a96b-4632-bfda-d1fae345974d@kernel.org \
--to=alx@kernel.org \
--cc=linux-man@vger.kernel.org \
--cc=tgolembi@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).