The attached command line application is a client reading one attribute for each of the ones specified in the command line.
For each command line argument, a thread is created and subscribe_event is called within the thread itself. It is easily demonstrated that as soon as the number of reading threads is greater than a single one, the events stop working, as you can see in the pictures that you can find in the same archive.
The application is the simplest Qt CLI application possible to reproduce the issue.
I think this bug has been hanging around for a long time but been masked by other issues causing the same symptoms. I marked it high priority cause it prevents tango events from working in a multi threaded environment.
Package versions under test:
tango-9.2.2
omni-4.2.1
zeromq-4.0.7
Building instructions:
qmake (qt5)
make
Hi Giacomo,
Thanks for the bug report.
I managed to reproduce the bug with your code and with tango 9.2.5.
I also found a small work-around.
I just added the following line at the end of the loop which creates all the threads:
On my test machine, this simple work-around seems to work.
I suspect there is a concurrency issue during the Tango event initialization phase or subscription phase...
We'll try to find a solution...
Cheers,
Reynald
Thank You Reynald.
Hope you are doing fine and let's stay in touch then!
let me know when something is ready to be tested!
P.S. write me to giacomo.strangolino@elettra.eu for work, here for
everything else!
Bye!
Giacomo.
Il giorno lun 30 gen 2017 alle ore 13:47 Bourtembourg Reynald bourtemb@users.sf.net ha scritto:
Hello everyone.
The attachment provided contains a simple pthread client that works the same way as the Qt example: one thread for each command line argument (device/attribute).
Qt is not involved this time, but the behavior is the same as it was in the Qt example, as you can see from the attached PNG pic (which you can find also in the tar/gz archive).
There are 4 threads involved, the main, the two created by the client, and a fourth where push_event is called.
pthread experts can dig into this simple code, but its correctness seems to be confirmed by the behaviour at exit time, which is as expected. The four threads are evidentiated by different colors. Note that subscribe and unsubscribe take place in the very same thread. As in the qt example, the push_event is called from another thread.
Giacomo.
Hi Giacomo,
Thank you very much for sending the pthread client.
There is an easy way to make the events work with this client.
Simply add
in ReadingPThread class.
This does not work with the Qt example. So the 2 clients are not fully equivalent.
I think this is because ReadingPThread constructor is invoked from a new thread in the pthread example whereas it is invoked from the main thread in the Qt example.
Adding
at the beginning of ReadingThread::run() method seems to solve the problem on the Qt example.
All these problems are coming from the fact that Tango is checking whether we are in the main thread or in another thread in various locations of the code but these checks are done using omni thread IDs. In the examples you gave, the threads which were created were not omnithreads, so they had no omnithread IDs...
Using omni_thread::ensure_self will create a dummy omni_thread for your current thread which will keep the same ID as long as the ensure_self object is alive.
I'll try to see whether there is a way to avoid to add the above line of code and I will investigate a bit deeper to ensure there is no side effect with the work around I suggested above...
Cheers,
Reynald
Hello Reynald!
Great!!
Thanks a lot!
Keep me up to date!
Giacomo
Il giorno mer 1 feb 2017 alle ore 11:35 Bourtembourg Reynald bourtemb@users.sf.net ha scritto:
Related
Bugs: #833
One observation.
In the pthread client example, I added a
omni_thread::ensure_self se;
as a class member, to ensure that its life lasts as long as the object exists.
Terminating the application produces in this case this output
terminate called after throwing an instance of 'omni_thread_invalid'
Aborted (core dumped)
2 0x00007fa393d714ed in __gnu_cxx::__verbose_terminate_handler () at /build/gcc/src/gcc/libstdc++-v3/libsupc++/vterminate.cc:95
3 0x00007fa393d6f2a6 in __cxxabiv1::__terminate (handler=<optimized out="">) at /build/gcc/src/gcc/libstdc++-v3/libsupc++/eh_terminate.cc:47</optimized>
4 0x00007fa393d6e239 in __cxa_call_terminate (ue_header=ue_header@entry=0xb14e50) at /build/gcc/src/gcc/libstdc++-v3/libsupc++/eh_call.cc:54
5 0x00007fa393d6ebed in __cxxabiv1::__gxx_personality_v0 (version=<optimized out="">, actions=<optimized out="">, exception_class=5138137972254386944, ue_header=<optimized out="">, context=0x7ffcbe6204a0)</optimized></optimized></optimized>
6 0x00007fa3937d4f43 in _Unwind_RaiseException_Phase2 (exc=exc@entry=0xb14e50, context=context@entry=0x7ffcbe6204a0) at /build/gcc/src/gcc/libgcc/unwind.inc:62
7 0x00007fa3937d52ab in _Unwind_RaiseException (exc=exc@entry=0xb14e50) at /build/gcc/src/gcc/libgcc/unwind.inc:131
8 0x00007fa393d6f4fb in __cxxabiv1::__cxa_throw (obj=0xb14e70, tinfo=0x7fa3948dfcd0 <typeinfo for="" omni_thread_invalid="">, dest=0x0) at /build/gcc/src/gcc/libstdc++-v3/libsupc++/eh_throw.cc:82</typeinfo>
9 0x00007fa3946de9f1 in omni_thread::release_dummy() () from /usr/local/omniorb-4.2.1/lib/libomnithread.so.4
10 0x0000000000402d78 in omni_thread::ensure_self::~ensure_self (this=0x7fa38c000980, __in_chrg=<optimized out="">) at /usr/local/omniorb-4.2.1/include/omnithread.h:644</optimized>
11 0x0000000000402c15 in ReadingPThread::~ReadingPThread (this=0x7fa38c0008c0, __in_chrg=<optimized out="">) at readingpthread.cpp:124</optimized>
12 0x0000000000402c5c in ReadingPThread::~ReadingPThread (this=0x7fa38c0008c0, __in_chrg=<optimized out="">) at readingpthread.cpp:127</optimized>
13 0x000000000040319a in clean () at main.cpp:23
I guess it's normal in this case, but it would be perfect if we can avoid this behavior.
Thanks again.
Giacomo
Hello,
You're right.
If you put :
at the beginning of ReadingPThread::subscribe() method, it should work fine without the crash at exit. (Don't forget to remove the ensure_self ReadingPThread class member in this case).
Hoping this helps,
Reynald
Last edit: Bourtembourg Reynald 2017-02-02
Hello Reynald.
Yes, it helps, thank you very much!
Before adding this to the qtango framework (looks like a dirty hack to me),
I would like to understand what it actually does,
and why for example putting it as a class member behaves differently
respect to putting it in the subscribe method.
In both cases omni_thread::ensure_self se is created lives and is destroyed
in the same thread (correct me if I am wrong)
Thank You for providing this quick workaround and for any information you
will kindly give me.
Best regards, Giacomo.
Il giorno gio 2 feb 2017 alle ore 10:34 Bourtembourg Reynald bourtemb@users.sf.net ha scritto:
As I wrote before, using omni_thread::ensure_self will create a dummy omni_thread for your
current thread which will keep the same ID as long as the ensure_self object is alive.
The omnithread documentation states the following about release_dummy() function called from the destructor of ensure_self:
This explains why when you put the ensure_self object as a member of ReadingPThread class, you get the omni_thread_invalid exception which is thrown, because ~ReadingPThread destructor is called from the main class in your case, not from the created thread.
If you delete ReadingPThreads objects at the end of create_reader() function it won't crash, even if you put ensure_self as class member of ReadingPThread.
Cheers,
Reynald
Last edit: Bourtembourg Reynald 2017-02-02
Thank You Reynald.
I've just noticed that the deletion takes place in the main thread. I was
convinced it did not, but now the reason you addressed is clear to me.
One more thing. In Claudio's omni thread example, the events were received
in different threads, according to where they had done the subscription.
In the pthread/Qt examples, another thread involves push_event. Is this
(apparently weird) behavior in accord with Tango relying on omnithread and
not dealing with pthreads?
Thanks, Giacomo.
Il giorno gio 2 feb 2017 alle ore 11:32 Bourtembourg Reynald bourtemb@users.sf.net ha scritto:
I think you didn't send the code for the omnithread example... but according to the output you've sent, it seems like the only difference is for the first received events which correspond to the synchronous calls ocurring during the subscription.
I think the answer to your question might be found in the following section of the code with some comments illustrating why it was done like that:
https://github.com/tango-controls/cppTango/blob/tango-9-lts/cppapi/client/event.cpp#L1214
(cppapi/client/event.cpp lines 1214-1239)
Here are the comments:
I guess Claudio's omni thread example is going through this part of the code, which is creating a new thread for the event subscription because Tango thinks the subscription was done from a callback (which is probably wrong in this case). And since some deadlocks have been observed in the past in this particular case, tango is doing the subscription in another thread.
This is just my guess...
Is this really a problem?
Last edit: Bourtembourg Reynald 2017-02-02