Problem 2554

Summary: `G4OpenGLStoredQtViewer` assertion fails on debug build
Product: Geant4 Reporter: shangjiaxuan
Component: visualization/OpenGLAssignee: Laurent Garnier <laurent.garnier>
Status: ASSIGNED ---    
Severity: minor CC: Ben.Morgan, laurent.garnier
Priority: P4    
Version: 11.1   
Hardware: All   
OS: All   
Attachments: stacktrace

Description shangjiaxuan 2023-07-20 12:41:58 CEST
Created attachment 818 [details]
stacktrace

Debug assertion fails on debug build of Geant4 when /run/beamOn 10 returns (using example B1) with TBB multithreading with Qt 5.15.2 vs2022.

Console output:

ASSERT failure in QCoreApplication::sendEvent: "Cannot send events to objects owned by a different thread. Current thread 0x0x1a811d51ec0. Receiver '' (of type 'QGLWidget') was created in thread 0x0x1a879c26ff0", file kernel\qcoreapplication.cpp, line 558

Debug stacktrace attached.

The assertion happens in G4OpenGLStoredQtViewer::updateQWidget()'s repaint() call, which eventually calls the check function:

void QCoreApplicationPrivate::checkReceiverThread(QObject *receiver)
{
    QThread *currentThread = QThread::currentThread();
    QThread *thr = receiver->thread();
    Q_ASSERT_X(currentThread == thr || !thr,
               "QCoreApplication::sendEvent",
               QString::asprintf("Cannot send events to objects owned by a different thread. "
                                 "Current thread 0x%p. Receiver '%ls' (of type '%s') was created in thread 0x%p",
                                 currentThread, qUtf16Printable(receiver->objectName()),
                                 receiver->metaObject()->className(), thr)
               .toLocal8Bit().data());
    Q_UNUSED(currentThread);
    Q_UNUSED(thr);
}

The assertion is only present in debug mode, since QtGlobal header gives:

#if !defined(Q_ASSERT_X)
#  if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS)
#    define Q_ASSERT_X(cond, where, what) static_cast<void>(false && (cond))
#  else
#    define Q_ASSERT_X(cond, where, what) ((cond) ? static_cast<void>(0) : qt_assert_x(where, what, __FILE__, __LINE__))
#  endif
#endif

Release mode Qt binary skips the check and works.
Comment 2 John.Allison 2023-07-22 14:47:51 CEST
Hi shangjiaxuan

First, let me say, I am not an expert on Qt, and _definitely_ not an expert on Windows.

This is difficult. With Qt5.15.10, I do not see this in Debug, RelWithDebInfo nor Release on Mac 13.4.1 (22F82). It looks like it is something to do with changing threads. (The G4Vis System uses a special vis sub-thread to display trajectories during a run.) Maybe Windows is picky.

I will pass this on to our Qt expert.

John
Comment 3 shangjiaxuan 2023-07-24 09:50:47 CEST
I think this may be relevant: 4.10.6 didn't experience this problem. There's this output on startup (after available ui sessions):

QObject::connect: No such signal QTabWidget::tabCloseRequested(G4int)
QObject::connect: No such signal QTabWidget::currentChanged(G4int)
QObject::connect: No such signal QTabWidget::currentChanged(G4int)
QObject::connect: No such signal QComboBox::activated(G4int)

I'm using qt 5.15.2, so these signals should be present, but these are not found. I believe there may be cmake qt configuration problems present? Or is it that qt not recognizing G4int typedef?
Comment 4 shangjiaxuan 2023-07-24 10:05:11 CEST
The `QObject::connect: No such signal` output comes from `G4interfaces`'s `G4UIQt.cc`. Editing the line 

```
  connect(fThreadsFilterComboBox, SIGNAL(activated(G4int)), this, SLOT(ThreadComboBoxCallback(G4int)));
```
to
```
  connect(fThreadsFilterComboBox, SIGNAL(activated(int)), this, SLOT(ThreadComboBoxCallback(G4int)));
```
changes the output to:
```
QObject::connect: No such signal QTabWidget::tabCloseRequested(G4int)
QObject::connect: No such signal QTabWidget::currentChanged(G4int)
QObject::connect: No such signal QTabWidget::currentChanged(G4int)
QObject::connect: Incompatible sender/receiver arguments
        QComboBox::activated(int) --> G4UIQt::ThreadComboBoxCallback(G4int)
```
So I believe types are considered incompatible in the configured build system. Also these lines may have been initialized in another thread (I'm not sure...), creating that problem (or maybe cross dll primitives are handled differently?).
Comment 5 shangjiaxuan 2023-07-24 10:15:48 CEST
Changing the signature completely removed that line of warning. Now it is:
```
Available UI session types: [ Qt, Win32, csh ]
QObject::connect: No such signal QTabWidget::tabCloseRequested(G4int)
QObject::connect: No such signal QTabWidget::currentChanged(G4int)
QObject::connect: No such signal QTabWidget::currentChanged(G4int)

**************************************************************
 Geant4 version Name: geant4-11-01-patch-01 [MT]   (10-February-2023)
  << in Multi-threaded mode >>
                       Copyright : Geant4 Collaboration
                      References : NIM A 506 (2003), 250-303
                                 : IEEE-TNS 53 (2006), 270-278
                                 : NIM A 835 (2016), 186-225
                             WWW : http://geant4.org/
**************************************************************

// Other items

=========================================================================
--> G4TaskRunManager::CreateAndStartWorkers() --> Initializing workers...
=========================================================================

Visualization Manager instantiating with verbosity "warnings (3)"...
Visualization Manager initialising...
Registering graphics systems...

You have successfully registered the following graphics systems.
Registered graphics systems are:
  ASCIITree (ATree)
  DAWNFILE (DAWNFILE)
  G4HepRepFile (HepRepFile)
  RayTracer (RayTracer)
  VRML2FILE (VRML2FILE)
  gMocrenFile (gMocrenFile)
  TOOLSSG_OFFSCREEN (TSG_OFFSCREEN)
  TOOLSSG_OFFSCREEN (TSG_OFFSCREEN, TSG_FILE)
  OpenGLImmediateQt (OGLIQt, OGLI)
  OpenGLStoredQt (OGLSQt, OGL, OGLS)
  Qt3D (Qt3D)
  TOOLSSG_QT_GLES (TSG_QT_GLES, TSGQt, TSG)
```
Part of UI is somehow initialized before visualization threads are created. Likely due to initialization order of static variables?
Comment 6 shangjiaxuan 2023-07-24 10:41:04 CEST
After looking into code, it seems 4.10.6 had similar code, only the connect functions signatures are changed. The assertion fail is likely not related.
Comment 7 shangjiaxuan 2023-07-25 10:00:21 CEST
Adding breakpoints shows that normal G4OpenGL refresh happens with main thread's `SessionStart` function. Release mode normal refresh after beamOn happens in `fpVisManager -> EndOfRun ();` in `G4VisStateDependent.cc`.

Rotation operations are also handled in the main thread session processing, not in vis thread. It's wierd that Qt internally issues events in a repaint function (mark-dirty, and send repaint event). I may have used a different Qt5 version when using Geant4.10.6. This is likely a behavior change or bug in Qt, or maybe a different refresh strategy used in Qt window?

The reason of the thread checking is that although messaging is inherently different from drawing, they are often coupled in libraries that support MacOS (where they cannot be separated). MacOS can only process system window events on main gcd thread, windows on the thread the window is created, whereas linux events are cross-thread.
Comment 8 shangjiaxuan 2023-07-25 10:01:38 CEST
The assertion failure happens in vis thread, which updates after each trajectory is drawn.
Comment 9 shangjiaxuan 2023-07-25 10:06:11 CEST
A workaround may be enabling realtime drawing at begin of run, and disabling at end of run. realtime drawing  is defined as drawing at max framerate (poll event, draw loop instead of draw event loop in main thread), and drawing whatever is available. This may also remove the window freeze when running a few events in gui mode.
Comment 10 shangjiaxuan 2023-07-25 11:48:59 CEST
Adding the following check reveals another problem in the Qt viewer.

```
#ifdef G4MULTITHREADED
  if (G4Threading::IsMasterThread())
#endif
  repaint(); // will read scene tree state
```
The field `lWaitForVisSubThreadQtOpenGLContextMoved` is locked when at least one run has been run, but never unlocked. This has no problem when running, but will throw exception at clean up code in debug mode, where   
```
delete lWaitForVisSubThreadQtOpenGLContextMoved;
```
throws exception because the lock being deleted is locked.

Since the lock is used in conjuction with a condition variable to use as semaphore, unlocking after the wait fixes this without introducing issues.
Comment 11 Ben Morgan 2023-07-28 15:39:53 CEST
Two additional things it would be useful for us to know are

1. If this also occurs with builds where the TBB backend to multithreading is *not* used. Can you confirm if the issues are still present in this configuration please?

2. Which G4RunManager is being used - G4MTRunManager or G4TaskRunManager.

These will help narrow what to look at.