John Wood
John Wood

Reputation: 43

How to interrupt other std::threads c++

I have a server which is built in a thread-per-client way. Lately, I bumped into a problem that I'm having a really hard time to come up with a solution to, so I thought to ask some help.

My server hosts a lobby, in the lobby there are many rooms (all of them owned by users), and in rooms there are players. Each room has an admin, when the admin chooses to leave - the room is closed, and all of the users are supposed to return to the lobby.

Now, I already have a working code - but there's the problem, I have no idea how should I have the other clients also exiting from the room. The code running in the thread is as following:

while(in_lobby)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}

while(in_room)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}

while(in_game)
{
//When a game started
//Not practical right now, though
}

There is no problem messing with the booleans from a thread of one client to another (because they are not exactly local variables, they are several conditions that I could change through the thread which is handling the administrator choosing to close the room).

The problem occurs when the condition DOES change, and the while loop is supposed to exit before the next iteration. Why does it occur? Because in the beginning of the iteration there's a recv() call, which awaits the client's messages.

Now, the condition is okay, everything is okay, but the loop won't continue (so it won't reach the next iteration - to see that the condition is false) until the server receives a certain message from the client (which it shouldn't, because closing the room doesn't depend on a regular user - the user only receives and alert, which is also sent through the admin's thread, about the room being closed).

My question therefore is:

How can I perform what I want? How can I make these users to break out of the loop, returning to the lobby (there is no problem with doing so with the admin, as his thread is the one that does everything and he returns to the lobby successfully) without changing the whole architecture from a thread-per-client way?

Upvotes: 4

Views: 2216

Answers (5)

lewis
lewis

Reputation: 1263

The Stroika thread library - https://github.com/SophistSolutions/Stroika/blob/v2.1-Release/Library/Sources/Stroika/Foundation/Execution/Thread.h - supports interruption through cancelation points. This solves about 1/2 of the problem.

But what solves the rest - for networking applications - is that the networking library provide wrappers on networking objects like sockets (https://github.com/SophistSolutions/Stroika/blob/v2.1-Release/Library/Sources/Stroika/Foundation/IO/Network/Socket.h) - which automatically transform blocking operations into cancelation points. This allows you to write your networking applications simply, and just tell a thread to cancel/abort, and any ongoing networking calls are canceled automatically (by the library).

Upvotes: 0

David Schwartz
David Schwartz

Reputation: 182753

Refactor your code so that there's one and only one place where you block on recv. Then you can move the client around all you want without having to disrupt the thread blocked in recv. You still want to receive a message if the client sends one, right?

So when a room is closed, the thread that closes the room can move clients out of it with no need to disturb the threads waiting for messages from those clients.

Upvotes: 0

user1084944
user1084944

Reputation:

Because in the beginning of the iteration there's a recv() call, which awaits the client's messages.

I'm not familiar with the particulars if what you're using, but two tricks that can be used in similar circumstances are:

  • Have your server put bytes into the stream that recv() reads from.
  • Close the stream.

With the intent that either of these will force recv() to return.

If you can't do this with winsock, then another solution is to add another layer. You write a wrapper class that manages the complexities of how to read from a socket while still being able to receive notifications from other sources. Then your clients only use the wrapper class for receiving communications.

In the long term, this solution is probably better than using recv() directly anyways, since it separates concerns; client code is only worried about how to deal with the client and not the details of how to do robust communications, and the communications code only has to deal with how to receive and relay communications.

Upvotes: 1

πάντα ῥεῖ
πάντα ῥεῖ

Reputation: 1

"Because in the beginning of the iteration there's a recv() call, which awaits the client's messages."

Probably you don't want to have a blocking recv() call at this point in 1st place.
There's the select() function from the Windows Socket API, you can use to observe the state changes for a number of socket file descriptors.

Basically you will have a thread running a loop, and poll the return value of a select() call. Not to hog the CPU with a tight loop, a reasonable timeout value should be specified, but it's possible with a zero timeout, and just poll the available states as well.

If the return value signals that there's some action available for one of the observed sockets (select() > 0), you can inspect the observed socket file descriptors if they were triggered using the

FD_ISSET(s, *set)

macro.

Upvotes: 1

Raydel Miranda
Raydel Miranda

Reputation: 14360

Besides πάντα ῥεῖ's comment, you could try to implement a Provider-Consumer pattern.

Use a queue for store clients messages and read from it using that loop, if no messages to read just continue. This way the loop don't waits for the message arrives, that is the "Provider's" Job (the loop is the consumer since is consuming messages from the queue).

So, move the code calling recv out of the loop and use it for feed the queue.

Pseudocode:

Queue queue; // This has to be thread save.

class Provider: Thread
{
    void run(queue)
    {
      while(1)
      {
          message = recv();       // This is where waiting occurs.
          queue.push(message);
      }
    }   
}


// This looks like it fits inside another thread. ;)
while (some_condition)
{
    message = queue.pop(); // This returns immediately.
    if (message)
    {
      //... so some things.  
    }
}

Upvotes: 0

Related Questions