views:

149

answers:

2

I have a supposedly single-threaded FLTK application with a popup menu, created with Fluid. I have a class that sub-classes Fl_Gl_Window and implements a handle() method. The handle() method calls a function that creates a popup window on right-click. I have a long operation that I do for one of the menu items. My application has created a second thread for some other purpose. I use locks to protect some critical sections between my main thread and that second thread. In particular, doLongOperation() uses the locks.

My problem is that I can popup the menu twice and run doLongOperation() twice, and it then deadlocks with itself, hanging the application. Why doesn't the first doLongOperation() stall the GUI and prevent me from starting doLongOperation() a second time?

I can avoid the problem with a flag that I use to disable the offending menu item, but I would like to understand why it is possible in the first place.

Here's the code, abbreviated of course. Hopefully I've included all the relevant bits.

class MyClass {
  void doLongOperation();
};

class MyApplication : public MyClass {
  MyApplication();
  void run();
  void popup_menu();
};

void MyClass::doLongOperation()
{
   this->enterCriticalSection();
   // stuff
   // EDIT
   // @vladr I did leave out a relevant bit.
   // Inside this critical section, I was calling Fl::check().
   // That let the GUI handle a new popup and dispatch a new
   // doLongOperation() which is what lead to deadlock.
   // END EDIT
   this->leaveCriticalSection();
} 

MyApplication::MyApplication() : MyClass() 
{
  // ...
  { m_mainWindowPtr = new Fl_Double_Window(820, 935, "Title");
    m_mainWindowPtr->callback((Fl_Callback*)cb_m_mainWindowPtr, (void*)(this));
    { m_wireFrameViewPtr = new DerivedFrom_Fl_Gl_Window(10, 40, 800, 560);
      // ...
    }
    m_mainWindowPtr->end();
  } // Fl_Double_Window* m_mainWindowPtr

m_wireFrameViewPtr->setInteractive();

m_mainWindowPtr->position(7,54);
m_mainWindowPtr->show(1, &(argv[0]));

Fl::wait();
}

void MyApplication::run() {
  bool keepRunning = true;
  while(keepRunning) {

  m_wireFrameViewPtr->redraw();
  m_wireFrameView2Ptr->redraw();

  MyClass::Status result = this->runOneIteration();
  switch(result) {
  case DONE: keepRunning = false; break;
  case NONE: Fl::wait(0.001); break;
  case MORE: Fl::check(); break;
  default: keepRunning = false;
  }

}

void MyApplication::popup_menu() {
  Fl_Menu_Item *rclick_menu;


  int longOperationFlag = 0;
  // To avoid the deadlock I can set the flag when I'm "busy".
  //if (this->isBusy()) longOperationFlag = FL_MENU_INACTIVE;

  Fl_Menu_Item single_rclick_menu[] = {
     { "Do long operation", 0, 0, 0, longOperationFlag },
     // etc. ...
     { 0 }
  };

  // Define multiple_rclick_menu...

  if (this->m_selectedLandmarks.size() == 1) rclick_menu = single_rclick_menu;
  else rclick_menu = multiple_rclick_menu;

  const Fl_Menu_Item *m = rclick_menu->popup(Fl::event_x(), Fl::event_y(), 0, 0, 0);

  if (!m) return;


  if (strcmp(m->label(), "Do long operation") == 0) {
    this->doLongOperation();
    return;
  }

  // Etc.

}
+1  A: 

By any chance, does your doLongOperation do anything that might need the message pump (or APCs, some windows' file API uses these underneath) to be running (assuming you see this behavior on Windows)? For example, if the doLongOperation tries to update the GUI that uses SendMessage underneath, you will get the deadlock even in a single-threaded scenario.

Also, does another thread have the critical section already claimed? You should be able to break in the debugger during the hang and hopefully see who is waiting on what.

Chris O
+2  A: 

Make sure that you are not calling Fl::wait(...) from more than one thread. Am I right in inferring from your code that run() executes in its own thread?

A first call to Fl::wait(), e.g. from the main thread, would catch and process the first right-click (blocking, as expected, while the first call to doLongOperation() proceeds); in the meantime, a second thread's calls to e.g. Fl::wait(timeout)/Fl::check() would continue to refresh the display -- and will intercept (and service) the second right-click, calling handle() (in the second thread) while the first long operation is still trudging along. This would give the appearance of a deadlock, although I expect that the UI would resume redrawing (through the second thread) when both long operations complete.

Validate the above by logging the current thread ID inside popup_menu().

You should pick a single thread to call Fl::wait(...) in a loop, and you should not block that loop -- spawn any non-modal or non-UI tasks as separate threads. I.e. when popup_menu() gets called, kick off the long operation in its own thread; if popup_menu() is triggered (again) while the long operation thread is still running, either mark the popup menu item as disabled (analogous to your workaround), or simply signal the long operation thread to restart with a new parameter.

Cheers, V.

vladr
I wasn't calling wait() or check() from more than one thread, but I was calling Fl::check() inside doLongOperation(). It was inside another function, so it was out of sight and out of mind. That Fl::check() let the GUI handle events, which let me start another doLongOperation(). That second one blocked waiting for the first to release the lock, but the first was blocked while the second was running.So, your answer wasn't the exact solution, but your meta-point ("be careful of wait() and check()") was the trigger for figuring out where I went wrong, so you get the bounty. Thanks!
cape1232
@vladr Sorry about the 1/2 bounty. I thought selecting your answer was enough to give it to you. I missed where I was supposed to do it explicitly. My first bounty, plus error, and you suffer for it. Life is so unfair ... to you.
cape1232