1

I have a problem with terminating sections in a C program. After catching a SIGINT signal in one thread I wanted to exit all threads and I don't really know how because I have infinite loops in these loops. The program waits for input from server or stdin. So I used signal handler.

I don't really know if I am doing this right way and I don't really understand how cancel in OpenMP works. I didn't find a proper tutorial or lecture for this.

My task is after catching SIGINT signal, terminate the program. But when I use exit() in the handler it leaves un-freed memory obviously. I will be glad for any advise, thank you.

#pragma omp parallel num_threads(2)
{
    #pragma omp sections
    {
        #pragma omp section
        {
            void intHandler(int dummy) 
            {
                char * welcome1 = calloc(strlen(username)+14,sizeof(char));
                strcat(welcome1,username);
                strcat(welcome1," logged out\r\n");
                if(send(client_socket,welcome1,strlen(welcome1),0) < 0)
                {
                    callError("ERROR: cannot send socked");
                }
                free(welcome1);
                #pragma omp cancel section
            }
            signal(SIGINT, intHandler);
            int i = 0, j = 1;
            while(1)
            {
                str = (char*)malloc(sizeof(char));
                while((c = getc(stdin)) != '\n')
                {

                    str = (char*)realloc(str, j * sizeof(char));
                    str[i] = c;
                    i++;
                    j++;
                }
                str = (char*)realloc(str, j * sizeof(char));
                str[i] = '\0';
                if(strlen(str)!=0)
                {
                    bufferIn = message(username,str);
                    if(send(client_socket,bufferIn,strlen(bufferIn),0) < 0)
                    {
                        callError("ERROR: cannot send socked");
                    }
                    free(bufferIn);
                }
                free(str); i = 0; j = 1; 
            }
        #pragma omp cancellation point section
        }
        #pragma omp section
        {
            void intHandler(int dummy) 
            {
                char * welcome1 = calloc(strlen(username)+14,sizeof(char));
                strcat(welcome1,username);
                strcat(welcome1," logged out\r\n");
                if(send(client_socket,welcome1,strlen(welcome1),0) < 0)
                {
                    callError("ERROR: cannot send socked");
                }
                free(welcome1);
                #pragma omp cancel section
            }
            signal(SIGINT, intHandler);
            char buffer[4096];
            ssize_t length;
            int received = 0;
            int data_cap = 4096;
            while(1)
            {
                data = calloc(BUFFER_LEN,sizeof(char));
                while ((length = read(client_socket, buffer, BUFFER_LEN-1)) > 0)
                {
                    received += length;
                    buffer[length] = '\0';
                    if (received > data_cap)
                    {
                         data = realloc(data,sizeof(char) * data_cap * 2);
                         data_cap = data_cap * 2;
                    }
                    strcat(data, buffer); 
                    if(!isEnough(data))
                    {
                        break;
                    }
                }
                printf("%s", data);
                free(data); bzero(buffer,BUFFER_LEN); data_cap = 4096; received = 0; length = 0;
            }
        #pragma omp cancellation point section
        }
    }

} 
Toby
  • 9,696
  • 16
  • 68
  • 132
krakra
  • 35
  • 8
  • "But when I use exit() in the handler it leaves un-freed memory obviously." Umm, this makes no sense at all. Once the process exits all of its memory is freed by the OS. (Unless there are shared memory segments here that you failed to mention, of course). – Jim Cownie Apr 20 '17 at 09:39
  • I know but that is bad practise to leave memory leak handling to OS and for my purpose its really bad because i am running this program on really limited machine – krakra Apr 20 '17 at 11:41
  • @kakra "it is bad practise to leave memory leak handling to OS". I disagree. It is a waste of your effort and CPU time to use "free" to return allocated store to the heap when the whole address space is about to be torn down. It makes no difference at all to the OS whether the heap has data on its free list or not when the OS unmaps the pages, so you are simply wasting CPU time by calling free. – Jim Cownie Apr 24 '17 at 09:24

1 Answers1

1

This is actually super complicated, but let's just start simple.

  • It's #pragma omp cancellation point sections / #pragma omp cancel sections (mind the s).

  • You cannot use #pragma omp cancel across function boundaries.1

Let's say you could use cancel this way, cancellation is only checked for at specific cancellation points. So during a blocking read or getc, your threads will not be interrupted by a cancellation.

Signal handling

Signal handlers are setup per process, it is not deterministic at what thread a signal ends at. You should not try to call signal concurrently from multiple threads. Some implementations of signal even like multi-threaded programs at all. Instead, you should use sigaction, but still setup a global signal handler once before even spawning the worker threads.

There are certain restrictions on what you are allowed to do in a signal handler. Basically you must not access any global variables that are not of type volatile sig_atomic_t and call only async-signal-safe functions. You violate that in every single line of your signal handler.

In particular, you call send(client_socket) while either the same thread might just be interrupted while calling read(client_socket)2 or another thread calling read(client_socket) concurrently. Not sure what is worse, but even if send itself is async-signal-safe, I'd wager a wild guess, it's not safe in the suggested manner.

You see, having some reachable memory at the end of the process is absolutely the very least of your problems. You're not even allowed to call exit (you can call _exit though).

The usual way out, is to set make a global cancel flag of type volatile sig_atomic_t and set that within the signal handler as well as check for it in the worker loops. This should also work with OpenMP section/threads, but I would advise to add a #pragma omp atomic read/write seq_cst for any read/write to the flag. That may seem redundant, but I'm fairly sure that volatile sig_atomic_t only guarantees atomicity regarding interruption for signals, and not multithreading and in particular storage visibility. Unfortunately, you still have the issue that read and getc are blocking...

A sketch of a solution

So you would have to use some mechanism to make getc non-blocking or add a timeout to give your thread a chance to check for the cancel flag.

poll can give you a more elegant way out. You can replace the blocking part of both read and getc with poll - keep in mind however that this forces you to use stdin exclusively as a file descriptor, never as a FILE*. In preparation you make a pipe for each section whose output you include in the respective poll. Within the signal handler, that you setup only after the pipes is generated, you write to those pipes, indicating that the threads should shut down. If poll shows activity for those particular stop-pipe-fds, you exit the loop, and do your cleanup in the appropriate thread or after the parallel region. Apply synchronization as necessary.

Sorry to bring you the bad news. It's complicated and there is no simple, correct, copy-pasteable, solution. In any case, OpenMP cancellation is not the right thing to use here.

1: The standard is not quite explicit about that, it mandates that:

During execution of a construct that may be subject to cancellation, a thread must not encounter an orphaned cancellation point. That is, a cancellation point must only be encountered within that construct and must not be encountered elsewhere in its region.

However, since cancel regions are itself implicit cancellation points, I suppose one might imply that cancel constructs must always be within the lexical scoped of a explicit parallel region. Anyway, gcc won't let you compile a function like your signal handler with a cancel construct.

2: This is actually fine, but you have to at least manually restore errno because the write in your signal handler overwrites it. The read should conveniently return with EINTR.

Community
  • 1
  • 1
Zulan
  • 21,896
  • 6
  • 49
  • 109
  • Thank you a lot it helped me :) – krakra Apr 20 '17 at 11:41
  • @krakra very glad to hear that :-), I was afraid that my long text would discourage you from trying to solve this properly. Feel free to post any further progress you made based on this answer as a separate answer or accept mine if it essentially solved your issue. – Zulan Apr 20 '17 at 11:53
  • i did what you told me i got rid of handlers in threads and other bad stuff you told me i have in my code. i really didn't know what to do so i am very glad for your ansfer and i will use select() function with that blocking operations to hadle signal adn exit threads – krakra Apr 20 '17 at 12:20
  • I would recommend to use `poll` instead of `select`. It basically does the same thing, but has a better interface and is less dangerous. – Zulan Apr 20 '17 at 12:24
  • Just one last question do i have to make some correction with sections ? if i want to secure it from data race or other problems that could parralel programing possibly have ? or it hadle this proble openMP its self – krakra Apr 20 '17 at 12:48