4

I have made a thread which is able to read bluetooth messages sent from phone. My problem came when I realised I have no way of killing that thread safely. Method so far was user input which called exit() from within that thread, all weird stuff started happening when I moved that out of that thread and into the main (stack smashing detected and segmentation fault). Obviously this approach also wouldn't really scale well if I had another thread for wifi calls or just didn't want to exit everything altogether.

This is my code so far.

Struct for bluetooth related stuff to pass to the thread:

typedef struct {
    struct sockaddr_rc loc_addr;
    struct sockaddr_rc rem_addr;
    socklen_t opt;
    char blu_buffer[1024];
    int blu_sock;    
} Bluetooth_stuff;

Thread listening for new connections:

void * BluetoothListiner(void * argv){

    Bluetooth_stuff * bt_set = (Bluetooth_stuff * ) argv;

    int bytes_read;
    int local_client;   
    int option = 0; 

    listen(bt_set->blu_sock, 1);
    memset(bt_set->blu_buffer, 0, sizeof(bt_set->blu_buffer));

    while(true){

        local_client = accept(bt_set->blu_sock, (struct sockaddr *)&((*bt_set).rem_addr), &((*bt_set).opt));        

        // read data from the client
        bytes_read = read(local_client, bt_set->blu_buffer, sizeof(bt_set->blu_buffer));
        if( bytes_read > 0 ) {
            // process data
            }   
        }
        else{
            // accept again?
        }       

        // clear the buffer
        memset(bt_set->blu_buffer, 0, sizeof(bt_set->blu_buffer));

        // close connection
        close(local_client);

        usleep(10);
    }
}

Socket configuration:

void BluetoothSocketConfig(Bluetooth_stuff * bt_set){   

    // allocate socket
    bt_set->blu_sock = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);        
    bt_set->opt= sizeof(bt_set->rem_addr);

    (*bt_set).loc_addr.rc_family = AF_BLUETOOTH;
    (*bt_set).loc_addr.rc_bdaddr = *BDADDR_ANY;
    (*bt_set).loc_addr.rc_channel = (uint8_t) 1;        

    // bind socket to port 1 of the first available 
    bind(bt_set->blu_sock, (struct sockaddr *)&(bt_set->loc_addr), sizeof(bt_set->loc_addr));

}

Testing main:

int main(int argc, char *argv[]){       
    Bluetooth_stuff bt_set;     
    BluetoothSocketConfig(&bt_set);     
    pthread_create(&blu_listiner, NULL, BluetoothListiner, bt_set);
    while(1);  // I modify that for testing to exit, having a global flag also gave me segmentation errors somehow
}

Calling exit(1) doesn't clean up other threads and pthread_kill or pthread_cancel from another thread would often cause processes still running in the background preventing me from running the application again without manually killing processes with ps -A and kill -9 PROCESS.

Based on this article I figured that the best way to do that was to introduce an if statement within the thread and have it pthread_exit itself into the oblivion. Such as:

if(external_exit){
    ret2  = 200;
    pthread_exit(&ret2);
}

Now since read() is a blocking call, I could never arrive at if statement without something externally happening. This answer suggests using a timeout to achieve that and I followed the syntax from this answer to manage to break out from read() after a certain period. So I had this added before the main while loop of the thread ( i think this should only be defined once rather than at every loop, though not sure as couldn't find specific example).

Now my problem was that it would block on read() on the first loop, but then would skip it as if nothing and just loop indefinitely. I have tried to "reenable read()", by reinvoking accept() listen() and bind() with no success. It seems using select() is the preferred option for first checking if there is anything to read, but as stated in the manual

"Under Linux, select() may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks. This could for example happen when data has arrived but upon examination has wrong checksum and is discarded"

So even if using it (which I'm not sure would work on bluetooth sockets as it would on tcp ones) there is always a chance it would still get stuck on read() with no way of killing it safely.

1) How can I safely kill that thread (or any blocking thread)?
2) Is select() really needed for blocking approach as I do not have multiple connections?
3) What is the procedure to reenable proper use of read() in blocking mode after the initial timeout?
4) Besides different initialisation are all sockets basically equivalent unix/can/tcp/bluetooth?
5) Any other ways to get what I want or serious flaws in logic from my part?

Thank you for any help ahead, if anyone has a link to a bluetooth server thread implementation I'd be thrilled to see it (or any other socket unix/can/tcp).

mega_creamery
  • 667
  • 7
  • 19
  • `read()` is not always blocking (though blocking by default, which is Unix braindamage that continues to hurt everybody). You can set the file descriptor to nonblocking, then `read()` (or better use `recvfrom()` or `recvmsg()`) will not block. – EOF Apr 08 '20 at 22:44
  • This question is too unfocused. Please cut it down to one or break it into several. The question's current title [is easy enough to answer](https://stackoverflow.com/q/17822025/132382), but the body introduces segfaults on `exit` (bug), "re-enabling" `read` (what?), erroneous use of `pthread_kill` and `_cancel` (bug), a question about socket initialization with different families, and more. – pilcrow Apr 09 '20 at 14:14
  • accept is blocking too by default, if noone tried to connect the loop may get stuck, so cosequentially read is blocking.. – Swift - Friday Pie Apr 10 '20 at 03:20

1 Answers1

0
  1. Open the socket with SOCK_NONBLOCK and leave it in non-blocking mode for it's entire lifetime.
  2. During the initialization phase, call eventfd(0,0) to create another file descriptor.
  3. Instead of blocking on read, setup and call select to wait on both the eventfd file descriptor and the socket (pass NULL for the timeout). This can block forever.
  4. When select returns:
    • If the socket is readable, call read. You are correct that select could have signaled things incorrectly, but in that case you'll get a EAGAIN and can loop back and call select again. If read returns any other error, "handle" it by closing the connection, etc.
    • If the eventfd file descriptor is readable, close the connection and perform graceful shutdown
  5. On the other thread, where you were previously were trying to kill the blocking thead, you can now write to the eventfd file descriptor to "interrupt" the select operation.
Mikel Rychliski
  • 3,455
  • 5
  • 22
  • 29