Hi. I have written a small test socket program that uses a background thread that accepts tcp connections and adds them to a connection list that is accessed by main().

When a connection is opened, then later closed, recv() is called (see line highlighted in code below), which throws an exception stating that the socket is closed.

When I compile in Dev-C++ the exception thrown in the main() thread is caught in the background thread. -- Why is this??

When I compile in Visual Studio 6 the exception is caught in the main() thread as I expected.

I upgraded the Dev-c++ compiler from mingw 3.4.2 to 3.4.5, but the problem still exists.

Does anyone know why this is happening, and if there are any workarounds?

Thanks.

The file is shown following:
Note: the TcpServerSocket.accept() method returns a new open TcpClientObject. All member functions simply call a socket function using the handle stored in the object. Exceptions thrown are derived from std::exception, with what() implemented.
The 2 socket classes are in separate files included in the project during compilation.

#include <cstdlib>
#include <iostream>
#include <string>
#include <list>
#include <conio.h>
#include <stdio.h>

#include "../WindowsPorts/TcpClientSocket.h"
#include "../WindowsPorts/TcpServerSocket.h"

using namespace std;

list<TcpClientSocket*> skts;
bool abortProgram = false;

DWORD __stdcall acceptThread(void *_port){
   int port = *(int*)_port;
   try{
      cout << "Listening on " << port << endl;
      TcpServerSocket listenSocket(port);

      while(1){
         TcpClientSocket *acc = NULL;
         
         try{
            acc = listenSocket.accept();
            }
         catch(exception &x){
            cout << "acceptThread" << " (inner loop) Caught Exception: " << x.what() << endl;
            }

         if(acc){
            cout << "Accepted connection from " << acc->getHost() << ":" << acc->getPort() << endl;
            skts.push_back(acc);
            }

         Sleep(1);      // useless
         }
      }
   catch(exception &x){
      cout << "acceptThread" << " Caught Exception: " << x.what() << endl;
      cout << "Listening Port Closed. Quitting Thread." << endl;
      cin.get();
      abortProgram = true;
      Sleep(10000);
      }

   return 0;
   }

int main(int argc, char *argv[]){
   DWORD port = 12350;
   if(argc == 2){
      port = atoi(argv[1]);
      }
      
   try{
      
      CreateThread(0, 0, acceptThread, &port, 0, 0);

      TcpServerSocket listen(12300);

      list<TcpClientSocket*>::iterator socketInUse = skts.begin();

      while(!abortProgram){
         Sleep(1);
         char ch = 0;

         if(kbhit()){
            ch = getch();
            if(ch == 27){
               if(++socketInUse == skts.end()){
                  socketInUse = skts.begin();
                  }

               if(skts.size() > 0 && socketInUse != skts.end()){
                  cout << "Data will be directed to " << (*socketInUse)->getHost() << "                             \r";
                  }
                  
               ch = 0;
               }
            else{
               try{
                  if(skts.size() > 0 && socketInUse != skts.end()){
                     (*socketInUse)->send(&ch, 1);
                     }
                  }
               catch(exception &x){
                  cout << "main" << " Caught Exception: " << x.what() << endl;
                  }
               }
            }

         for(list<TcpClientSocket*>::iterator it = skts.begin(); it != skts.end(); it++){
            try{
               if((*it)->waitForData(10) == 1){    // got data, display it
                  char buff[1500];
// ########### the following line will throw the exception when the connection is closed (remotely)
                  unsigned int len = (*it)->recv(buff, sizeof(buff));

//                  cout << "read " << len << " bytes " << (len == 0) << endl;
                  if(len > 0)   
                     cout << (*it)->getHost() << ": " << string(buff, len) << endl;
                  }
               }
            catch(exception &x){
               cout << "main" << " Caught Exception: " << x.what() << endl;
               cout << "Closing port" << endl;

			      bool reset = false;
               if(socketInUse == it){
                  reset = true;
                  }

               delete *it;       // destroy object
               skts.erase(it);   // destroy reference

			      if(reset)
                  socketInUse = skts.end();

               break;
               }
            catch(...){
               cout << "main" << " Unhandled Exception" << endl;
               cout << "Closing port" << endl;

			      bool reset = false;
     			   if(socketInUse == it){
                  reset = true;
                  }

               delete *it;       // destroy object
               skts.erase(it);   // destroy reference
               
               if(reset)
                  socketInUse = skts.end();

               break;
               }
            }
         }
      }
   catch(exception &x){
      cout << "main" << " Caught Exception: " << x.what() << endl;
      }
   catch(...){
      cout << "main" << " Unhandled Exception" << endl;
      }

   system("PAUSE");
   return EXIT_SUCCESS;
   }

Dani AI

Generated

Good catch by — the symptom (an exception generated during a recv() ending up handled on the wrong thread) usually signals a runtime/ABI mismatch rather than a logic bug in the socket code. On Windows with GCC/MinGW the compiler/runtime must be told to use the MinGW thread-aware helpers so per-thread exception unwind state is maintained; otherwise unwinding can behave oddly. (manuals.plus)

Important: the C++ model treats exceptions as thread-local — an exception thrown on one thread is not catchable on another. Worker threads should therefore catch their own exceptions and communicate results or failure to the main thread (rethrowing across threads is not a supported mechanism). Capturing and rethrowing on the parent thread is the supported pattern. (docs.oracle.com)

A second source of trouble is mixing exception models or runtimes. GCC on Windows can be built with different EH models (SJLJ, DWARF/DW2, SEH); mixing object files or DLLs built with different models or with MSVC can break stack unwinding and cause surprising behavior. Ensuring a single, consistent exception model and runtime across the whole program (and all linked libraries) avoids these subtle failures. (stackoverflow.com)

Practical recommendations and a safe pattern:

  • Build all translation units and linked libraries with the same exception/thread model and runtime. A quick check of the compiler build/config is possible via gcc -v. (stackoverflow.com)
  • Catch exceptions inside the worker thread, capture them with std::current_exception() and pass an std::exception_ptr (or set an exception into a std::promise) to the main thread; rethrow with std::rethrow_exception() when needed. Example pattern:
    std::exception_ptr ep;
    std::thread t([&](){
    try { /* work that may throw */ }
    catch(...) { ep = std::current_exception(); }
    });
    t.join();
    if (ep) std::rethrow_exception(ep);

    std::exception_ptr and the promise/future mechanism are the standard ways to propagate failures between threads. (ld2015.scusa.lsu.edu)

Troubleshooting checklist: confirm all objects are built with the same GCC/MinGW distribution and EH model; avoid mixing MSVC-built C++ binaries; and prefer MinGW-w64 releases that match the target architecture and recommended EH model for that architecture. (sourceforge.net)

OK, I found out the compilation option -mthreads (MinGW specific) is needed to allow safe threaded code with exceptions. It may also be the case that -pthreads is required for non-MinGW gcc.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.