tango-controls/pytango

Windows GUI app crash on exit (Successful WSASTARTUP not yet performed)

Closed this issue · 8 comments

Hello.

We create GUI applications using Pytango and PyQt/PySide and use events to receive data.
When the program is closed via X (close) button, Windows show popup with "unknown software exception" and "Assertion failed: Successful WSASTARTUP not yet performed (......\src\signaler.cpp:377)" error in the terminal, even with unsubscribing from events before accepting close signal.

image

Possible solution may be described here zeromq/czmq#1788
Some more details are here #367

Windows 10, Python 3.7, PyTango 9.3.1, PySide2 5.15.1, Tango 9.3.4

@Diego91RA @reszelaz

PyTango doesn't have a direct hook to the ZMQ context in the cppTango library. From what I can see, the singleton classes ApiUtil (clients/device proxies) and Util (servers) own the ZMQ contexts. The ApiUtil has an event consumer, and the Util has an event supplier.

If your application is doing client access to Tango devices, then it might help to call tango.ApiUtil.cleanup() when your application is about to exit. That will free the ApiUtil singleton, which should also release its ZMQ context (cppTango code here and here).

If you are seeing this problem with PyTango device servers, then you may also need to call: tango.Util.instance().server_cleanup(). However, I'm not sure if that cleans up the ZMQ event supplier. From the cppTango code, it just mentions cleaning up the orb. I don't see a way to set the Util singleton to NULL.

If this works, please let me know. Maybe it will help to register an atexit call in the PyTango code on Windows?

Hi @ajoubertza,
Many thanks for these advices. We will certainly test it during the Jan21 (before end of January) release processes of Taurus and Sardana. These are the times we usually test on Windows. I will do my best to find some extra time earlier but I can not promise.

Hi @ajoubertza

I've tried to add tango.ApiUtil.cleanup() to the PyQt closeEvent() function in my client application and now it's just stuck on exit, without responding or showing error popup dialogs, like it was previously.

Thanks for trying it out, @Diego91RA. Maybe there is a different place to call it (fr example, using the Python atexit module)? Or maybe you have to somehow cleanup all your PyTango clients before the doing the tango.ApiUtil.cleanup()? You might even need to force a gc.collect() step as part of the cleanup.

Other than that, you would need to attach a debugger to see what is happening.

Hi @ajoubertza

I think PyQt doesn't implement Python atexit, it has its own on-close triggers, inherited from C++ Qt, such as closeEvent or QApplication.aboutToQuit.
I've tried different combinations of them and gc.collect() worked fine (and has no effect), but tango.ApiUtil.cleanup() was stuck anyway.

After that I've decided to comment line that switches my application to PUSH_CALLBACK model (self.api_util.set_asynch_cb_sub_model(tango.cb_sub_model.PUSH_CALLBACK)) and there are no problems on exit.

But now I'm confused because it was necessary to switch event system model if I want the callback to be executed immediately and not to pull data from event queue by code.
This code:

   for attr in self.attr_list:
       sub_id = self.dev.subscribe_event(attr,
           tango.EventType.PERIODIC_EVENT,
           self.recv_event)

works without model switch now, but default model is supposed to be PULL.

Are event system models deprecated for PyTango? And I can decide which one I want to use in the subscribe_event constructor?

@Diego91RA I haven't used that ApiUtil method, but from the docs and code, I think the set_asynch_cb_sub_model only applies to commands, not events.
cppTango details about commands: https://tango-controls.readthedocs.io/en/latest/development/client-api/cpp-client-programmers-guide.html#asynchronous-model
cppTango details about events: https://tango-controls.readthedocs.io/en/latest/development/client-api/cpp-client-programmers-guide.html#subscribing-to-events

We can see from those docs, that the parameters passed to the subscribe_event method are what determines "push" or "pull" mode. This isn't so clearly visible in the PyTango docs, but I think it is as follows:

For push model:

subscribe_event(self, attr_name, event, callback, filters=[], stateless=False, extract_as=Numpy, green_mode=None) -> int

For pull model:

subscribe_event(self, attr_name, event, queuesize, filters=[], stateless=False, green_mode=None) -> int`

From that, it depends on the 3rd argument - if it is a callable, then we use push, if it is an integer then we use pull. In your example, you provide self.recv_event, which is most likely a callable, so you are using the push model.

Hi @ajoubertza.
I've checked my old projects and found that I started using push/pull model switch in 2016, pytango 9.2.0 I think.
Maybe there was a bug, but that code followed me from one project to another since that.

Anyway, we've figured out tango.ApiUtil.cleanup() on exit fix the crash problem in my case.
@reszelaz can you check it in your developments?

Also, can we do it in Pytango itself?

@reszelaz can you check it in your developments?

Sure, we will try it soon. This month we plan to make release tests on Windows and will take a profit of these tests to try this solution.