napari-omero icon indicating copy to clipboard operation
napari-omero copied to clipboard

Connection Lost Exception: no clear way to reconnect

Open psobolewskiPhD opened this issue 10 months ago • 9 comments
trafficstars

After having connected to an OMERO server and idled for some time, I lost connection -- likely because of computer sleep. When returning to napari, I get the Connection Lost Exception. The image I had loaded is fine. Getting an exception is fine, but there is no clear path to recovery -- the Disconnect button remains, but does't work, and there is no reconnect/login option.

What would be nice is if the Disconnect button became Reconnect, prompting for a password if needed, but otherwise returning the session.

---------------------------------------------------------------------------
ConnectionLostException                   Traceback (most recent call last)
File ~/Documents/dev/napari-omero/src/napari_omero/widgets/tree_model.py:94, in OMEROTreeModel.fetchMore(self=<napari_omero.widgets.tree_model.OMEROTreeModel object>, index=<PyQt5.QtCore.QModelIndex object>)
     92 def fetchMore(self, index: QModelIndex) -> None:
     93     item = self.itemFromIndex(index)
---> 94     for child in item.yieldChildren():
        item = <napari_omero.widgets.tree_model.OMEROTreeItem object at 0x1759263b0>
     95         child_item = OMEROTreeItem(child)
     96         item.appendRow(child_item)

File ~/Documents/dev/napari-omero/src/napari_omero/widgets/tree_model.py:24, in OMEROTreeItem.yieldChildren(self=<napari_omero.widgets.tree_model.OMEROTreeItem object>)
     23 def yieldChildren(self):
---> 24     yield from self.wrapper._conn.getObjects(
        self.wrapper._conn = <omero.gateway._BlitzGateway object at 0x1759cae30>
        self = <napari_omero.widgets.tree_model.OMEROTreeItem object at 0x1759263b0>
        self.wrapper = <_ProjectWrapper id=1901>
     25         self.child_type,
     26         opts={
     27             self.wrapper_type.lower(): self.wrapper.id,
     28             "order_by": "obj.name",
     29         },
     30     )

File ~/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero/gateway/__init__.py:3309, in _BlitzGateway.getObjects(self=<omero.gateway._BlitzGateway object>, obj_type='Dataset', ids=None, params=object #0 (::omero::sys::Parameters)
{
    map =... }
    theFilter = <nil>
    theOptions = <nil>
}, attributes=None, respect_order=False, opts={'order_by': 'obj.name', 'project': 1901})
   3306 query, params, wrapper = self.buildQuery(
   3307     obj_type, ids, params, attributes, opts)
   3308 qs = self.getQueryService()
-> 3309 result = qs.findAllByQuery(query, params, self.SERVICE_OPTS)
        qs = <omero.gateway.ProxyObjectWrapper object at 0x1759cb3a0>
        query = 'select obj from Dataset obj join fetch obj.details.owner as owner join fetch obj.details.creationEvent join obj.projectLinks projectLinks where projectLinks.parent.id = :pid order by obj.name, obj.id'
        params = object #0 (::omero::sys::Parameters)
{
    map = 
    {
        key = pid
        value = object #1 (::omero::RLong)
        {
            _val = 1901
        }
    }
    theFilter = <nil>
    theOptions = <nil>
}
        self.SERVICE_OPTS = <ServiceOptsDict: {'omero.client.uuid': '26e367ce-f620-413c-8cf2-d8d26b4eb5e4', 'omero.session.uuid': 'a26417f0-1cf1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>
        self = <omero.gateway._BlitzGateway object at 0x1759cae30>
   3310 if respect_order and ids is not None:
   3311     idMap = {}

File ~/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero/gateway/__init__.py:4862, in OmeroGatewaySafeCallWrapper.__call__(self=<omero.gateway.OmeroGatewaySafeCallWrapper object>, *args=('select obj from Dataset obj join fetch obj.detai...tLinks.parent.id = :pid order by obj.name, obj.id', object #0 (::omero::sys::Parameters)
{
    map =... }
    theFilter = <nil>
    theOptions = <nil>
}, <ServiceOptsDict: {'omero.client.uuid': '26e367c...f1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>), **kwargs={})
   4860 except Exception as e:
   4861     self.debug(e.__class__.__name__, args, kwargs)
-> 4862     return self.handle_exception(e, *args, **kwargs)
        self = <omero.gateway.OmeroGatewaySafeCallWrapper object at 0x17fc219c0>
        args = ('select obj from Dataset obj join fetch obj.details.owner as owner join fetch obj.details.creationEvent join obj.projectLinks projectLinks where projectLinks.parent.id = :pid order by obj.name, obj.id', object #0 (::omero::sys::Parameters)
{
    map = 
    {
        key = pid
        value = object #1 (::omero::RLong)
        {
            _val = 1901
        }
    }
    theFilter = <nil>
    theOptions = <nil>
}, <ServiceOptsDict: {'omero.client.uuid': '26e367ce-f620-413c-8cf2-d8d26b4eb5e4', 'omero.session.uuid': 'a26417f0-1cf1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>)
        kwargs = {}

File ~/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero/gateway/__init__.py:4859, in OmeroGatewaySafeCallWrapper.__call__(self=<omero.gateway.OmeroGatewaySafeCallWrapper object>, *args=('select obj from Dataset obj join fetch obj.detai...tLinks.parent.id = :pid order by obj.name, obj.id', object #0 (::omero::sys::Parameters)
{
    map =... }
    theFilter = <nil>
    theOptions = <nil>
}, <ServiceOptsDict: {'omero.client.uuid': '26e367c...f1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>), **kwargs={})
   4857 def __call__(self, *args, **kwargs):
   4858     try:
-> 4859         return self.f(*args, **kwargs)
        self.f = <bound method IQueryPrx.findAllByQuery of a26417f0-1cf1-478f-9f06-7c1b5a8ce158/26e367ce-f620-413c-8cf2-d8d26b4eb5e4omero.api.IQuery -t -e 1.1:tcp -h 192.168.228.191 -p 35511 -t 60000>
        self = <omero.gateway.OmeroGatewaySafeCallWrapper object at 0x17fc219c0>
        args = ('select obj from Dataset obj join fetch obj.details.owner as owner join fetch obj.details.creationEvent join obj.projectLinks projectLinks where projectLinks.parent.id = :pid order by obj.name, obj.id', object #0 (::omero::sys::Parameters)
{
    map = 
    {
        key = pid
        value = object #1 (::omero::RLong)
        {
            _val = 1901
        }
    }
    theFilter = <nil>
    theOptions = <nil>
}, <ServiceOptsDict: {'omero.client.uuid': '26e367ce-f620-413c-8cf2-d8d26b4eb5e4', 'omero.session.uuid': 'a26417f0-1cf1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>)
        kwargs = {}
   4860     except Exception as e:
   4861         self.debug(e.__class__.__name__, args, kwargs)

File ~/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero_api_IQuery_ice.py:710, in IQueryPrx.findAllByQuery(self=a26417f0-1cf1-478f-9f06-7c1b5a8ce158/26e367ce-f6...t -e 1.1:tcp -h 192.168.228.191 -p 35511 -t 60000, query='select obj from Dataset obj join fetch obj.detai...tLinks.parent.id = :pid order by obj.name, obj.id', params=object #0 (::omero::sys::Parameters)
{
    map =... }
    theFilter = <nil>
    theOptions = <nil>
}, _ctx=<ServiceOptsDict: {'omero.client.uuid': '26e367c...f1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>)
    709 def findAllByQuery(self, query, params, _ctx=None):
--> 710     return _M_omero.api.IQuery._op_findAllByQuery.invoke(self, ((query, params), _ctx))
        self = a26417f0-1cf1-478f-9f06-7c1b5a8ce158/26e367ce-f620-413c-8cf2-d8d26b4eb5e4omero.api.IQuery -t -e 1.1:tcp -h 192.168.228.191 -p 35511 -t 60000
        ((query, params), _ctx) = (('select obj from Dataset obj join fetch obj.details.owner as owner join fetch obj.details.creationEvent join obj.projectLinks projectLinks where projectLinks.parent.id = :pid order by obj.name, obj.id', object #0 (::omero::sys::Parameters)
{
    map = 
    {
        key = pid
        value = object #1 (::omero::RLong)
        {
            _val = 1901
        }
    }
    theFilter = <nil>
    theOptions = <nil>
}), <ServiceOptsDict: {'omero.client.uuid': '26e367ce-f620-413c-8cf2-d8d26b4eb5e4', 'omero.session.uuid': 'a26417f0-1cf1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>)
        _M_omero.api.IQuery._op_findAllByQuery = <IcePy.Operation object at 0x1032797b0>
        (query, params) = ('select obj from Dataset obj join fetch obj.details.owner as owner join fetch obj.details.creationEvent join obj.projectLinks projectLinks where projectLinks.parent.id = :pid order by obj.name, obj.id', object #0 (::omero::sys::Parameters)
{
    map = 
    {
        key = pid
        value = object #1 (::omero::RLong)
        {
            _val = 1901
        }
    }
    theFilter = <nil>
    theOptions = <nil>
})
        _ctx = <ServiceOptsDict: {'omero.client.uuid': '26e367ce-f620-413c-8cf2-d8d26b4eb5e4', 'omero.session.uuid': 'a26417f0-1cf1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>
        query = 'select obj from Dataset obj join fetch obj.details.owner as owner join fetch obj.details.creationEvent join obj.projectLinks projectLinks where projectLinks.parent.id = :pid order by obj.name, obj.id'
        params = object #0 (::omero::sys::Parameters)
{
    map = 
    {
        key = pid
        value = object #1 (::omero::RLong)
        {
            _val = 1901
        }
    }
    theFilter = <nil>
    theOptions = <nil>
}
        _M_omero.api = <module 'omero.api' from '/Users/sobolp/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero/api/__init__.py'>
        _M_omero = <module 'omero' from '/Users/sobolp/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero/__init__.py'>

ConnectionLostException: Ice.ConnectionLostException:
recv() returned zero
WARNING:omero.gateway:ConnectionLostException on <class 'omero.gateway.OmeroGatewaySafeCallWrapper'> to <26e367ce-f620-413c-8cf2-d8d26b4eb5e4omero.api.IQuery> findAllByQuery(('select obj from Dataset obj join fetch obj.details.owner as owner join fetch obj.details.creationEvent join obj.projectLinks projectLinks where projectLinks.parent.id = :pid order by obj.name, obj.id', object #0 (::omero::sys::Parameters)
{
    map = 
    {
        key = pid
        value = object #1 (::omero::RLong)
        {
            _val = 1901
        }
    }
    theFilter = <nil>
    theOptions = <nil>
}, <ServiceOptsDict: {'omero.client.uuid': '26e367ce-f620-413c-8cf2-d8d26b4eb5e4', 'omero.session.uuid': 'a26417f0-1cf1-478f-9f06-7c1b5a8ce158', 'omero.group': '-1'}>), {})
Traceback (most recent call last):
  File "/Users/sobolp/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero/gateway/__init__.py", line 4859, in __call__
    return self.f(*args, **kwargs)
  File "/Users/sobolp/micromamba/envs/napari-omero/lib/python3.10/site-packages/omero_api_IQuery_ice.py", line 710, in findAllByQuery
    return _M_omero.api.IQuery._op_findAllByQuery.invoke(self, ((query, params), _ctx))
Ice.ConnectionLostException: Ice.ConnectionLostException:
recv() returned zero

psobolewskiPhD avatar Jan 07 '25 21:01 psobolewskiPhD

I ran into the same problem recently, you beat me to creating the issue. One way of fixing it would be to start a thread that checks whether the connection is still open at a regular time interval. If that would return False, the thread would have to trigger the teardown action associated to the Disconnect button and exit itself.

Unfortunately, I don't know how to edit the timeout of the omero Python client so my only way of testing this at the moment is to sit around for ~10minutes to see whether the reconnect faills ^^"

Edit: Maybe this fixes it.

jo-mueller avatar Jan 08 '25 11:01 jo-mueller

@jo-mueller: happy to get you setup with a test server that has reduced connection timeout settings if it'll help.

https://omero.readthedocs.io/en/stable/sysadmins/config.html#omero-sessions-timeout e.g. is a place to start, e.g., by setting CONFIG_omero_sessions_timeout=60000 on the omero server docker: https://github.com/ome/omero-server-docker/blob/master/README.md#omeroserver-docker

joshmoore avatar Jan 08 '25 15:01 joshmoore

Hi @joshmoore thanks for chiming in. I have also set myself up a dockerized omero test instance - good to know how to set the timeout on the serverside 👍

I think for testing purposes and to see how the plugin handles timeout, I'll set it to something really low and take it from there. Hope this helps to engineer a clean exit in case the gateway does timeout, anyway.

jo-mueller avatar Jan 08 '25 15:01 jo-mueller

Sounds good. There's a secondary timeout within the glacier2router process, but I assume you're hitting the server-side one.

joshmoore avatar Jan 08 '25 16:01 joshmoore

@joshmoore

happy to get you setup with a test server that has reduced connection timeout settings if it'll help.

On that note: Do you think there's any possibility to have such an omero instance running to be able to do some proper CI testing for omero-based tools? From what I've seen here but also at omero-py or ezomero is that it's terribly hard to maintain good coverage and testing - probably due to not being able to test most functionality in a CI pipeline 🤔

jo-mueller avatar Jan 08 '25 19:01 jo-mueller

ezomero runs an omero server within its tests... https://github.com/TheJacksonLaboratory/ezomero/blob/main/.github/workflows/run_tests_pr.yml

see the docker compose in /tests

psobolewskiPhD avatar Jan 08 '25 20:01 psobolewskiPhD

I think, I have a rough working version, but what I'm lacking - what's a propper way to check whether the connection is still up? E.g., if I'd use ezomero to spin up a conn object similar to how napari-omero does it:

conn = ezomero.connect(...)

annd then turn off my VPN which I use to tunnel into my institution network or go to airplane mode on my laptop, the property conn.isConnected() never goes to False 🤔 Am I missing something in how the BlitzGateway actually works? I would expect that isConnected does a check under the hood whether the remote server still replies but apparently I'm misstaken there...

Omero-py version is 5.19.5, for reference

jo-mueller avatar Jan 08 '25 22:01 jo-mueller

cc: @erickmartins

joshmoore avatar Jan 09 '25 10:01 joshmoore

Ok, I think I got it. The BlitzGateway is a static object, so actively tracking its own connection is probably difficult. I built in a threaded function that checks continuously whether the host can be reached and triggers the disconnection if it doesn't.

jo-mueller avatar Jan 10 '25 22:01 jo-mueller