Skip to content

Commit cb63103

Browse files
Use connection manager for keepalives (#20)
1 parent bf45593 commit cb63103

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,24 @@ with omero2pandas.OMEROConnection(server='my.server', port=4064,
166166

167167
The context manager will handle session creation and cleanup automatically.
168168

169+
### Connection Management
170+
171+
omero2pandas keeps track of any active connector objects and shuts them down
172+
safely when Python exits. Deleting all references to a connector will also
173+
handle closing the connection to OMERO gracefully. You can also call
174+
`connector.shutdown()` to close a connection manually.
175+
176+
By default omero2pandas also keeps active connections alive by pinging the
177+
server once per minute (otherwise the session may timeout and require
178+
reconnecting). This can be disabled as follows
179+
180+
```python
181+
omero2pandas.connect_to_omero(keep_alive=False)
182+
```
183+
184+
N.b. omero2pandas uses a different system from the native OMERO API's
185+
`client.enableKeepAlive` function, using both is unnecessary.
186+
169187
### Querying tables
170188

171189
You can also supply [PyTables condition syntax](https://www.pytables.org/usersguide/condition_syntax.html) to the `read_table` and `download_table` functions.

omero2pandas/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ def _validate_requested_object(file_id, annotation_id):
377377

378378
def connect_to_omero(client=None, server=None, port=4064,
379379
username=None, password=None, session_key=None,
380-
allow_token=True, interactive=True):
380+
allow_token=True, interactive=True, keep_alive=True):
381381
"""
382382
Connect to OMERO and return an OMEROConnection object.
383383
:param client: An existing omero.client object to be used instead of
@@ -391,12 +391,13 @@ def connect_to_omero(client=None, server=None, port=4064,
391391
:param allow_token: True/False Search for omero_user_token before trying
392392
to use credentials. Default True.
393393
:param interactive: Prompt user for missing login details. Default True.
394+
:param keep_alive: Periodically ping the server to prevent session timeout.
394395
:return: OMEROConnection object wrapping a client and Blitz Gateway object,
395396
with automatic session management and cleanup.
396397
"""
397398
connector = OMEROConnection(server=server, port=port,
398399
session_key=session_key, username=username,
399400
password=password, client=client,
400401
allow_token=allow_token)
401-
connector.connect(interactive=interactive)
402+
connector.connect(interactive=interactive, keep_alive=keep_alive)
402403
return connector

omero2pandas/connect.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@
1010
import getpass
1111
import importlib.util
1212
import logging
13+
import threading
14+
import time
1315
import weakref
1416

17+
import Ice
1518
import omero
1619
from omero.gateway import BlitzGateway
1720

1821
LOGGER = logging.getLogger(__name__)
19-
2022
ACTIVE_CONNECTORS = weakref.WeakSet()
23+
KEEPALIVE_THREAD = None
2124

2225

2326
class OMEROConnection:
@@ -83,6 +86,7 @@ def shutdown(self):
8386
LOGGER.debug("Closing OMERO session")
8487
self.client.closeSession()
8588
self.client = None
89+
self.session = None
8690

8791
def __del__(self):
8892
# Make sure we close sessions on deletion.
@@ -104,7 +108,7 @@ def need_connection_details(self):
104108
return True
105109
return False
106110

107-
def connect(self, interactive=True):
111+
def connect(self, interactive=True, keep_alive=True):
108112
if self.connected:
109113
return True
110114
# Attempt to establish a connection.
@@ -122,25 +126,33 @@ def connect(self, interactive=True):
122126
if self.session_key is not None:
123127
try:
124128
self.client.joinSession(self.session_key)
129+
self.session = self.client.getSession()
130+
self.session.detachOnDestroy()
125131
except Exception as e:
126132
print(f"Failed to join session, token may have expired: {e}")
127133
self.client = None
134+
self.session = None
128135
return False
129136
elif self.username is not None:
130137
try:
131138
self.session = self.client.createSession(
132139
username=self.username, password=self.password)
133-
self.client.enableKeepAlive(60)
134140
self.session.detachOnDestroy()
135141
except Exception as e:
136142
print(f"Failed to create session: {e}")
137143
self.client = None
144+
self.session = None
138145
return False
139146
else:
140147
self.client = None
148+
self.session = None
141149
raise Exception(
142150
"Not enough details to create a server connection.")
143151
print(f"Connected to {self.server}")
152+
if keep_alive:
153+
# Use o2p keep alive instead of omero-py
154+
self.client.stopKeepAlive()
155+
start_keep_alive()
144156
return True
145157

146158
def connect_widget(self):
@@ -268,6 +280,15 @@ def get_client(self):
268280
LOGGER.warning("Client connection not initialised")
269281
return self.client
270282

283+
def keep_alive(self):
284+
if self.client is not None and self.session is not None:
285+
try:
286+
self.session.keepAlive(None)
287+
except Ice.CommunicatorDestroyedException:
288+
self.session = None # Was shut down
289+
except Exception as e:
290+
LOGGER.warning(f"Failed to keep alive: {e}")
291+
271292

272293
def detect_jupyter():
273294
# Determine whether we're running in a Jupyter Notebook.
@@ -293,4 +314,21 @@ def cleanup_sessions():
293314
connector.shutdown()
294315

295316

317+
def keep_sessions_alive():
318+
while ACTIVE_CONNECTORS:
319+
time.sleep(60)
320+
for connector in ACTIVE_CONNECTORS:
321+
connector.keep_alive()
322+
connector = None # Don't keep a reference (would prevent shutdown!)
323+
324+
325+
def start_keep_alive():
326+
global KEEPALIVE_THREAD
327+
if KEEPALIVE_THREAD is None or not KEEPALIVE_THREAD.is_alive():
328+
KEEPALIVE_THREAD = threading.Thread(target=keep_sessions_alive,
329+
name="omero2pandas_keepalive",
330+
daemon=True)
331+
KEEPALIVE_THREAD.start()
332+
333+
296334
atexit.register(cleanup_sessions)

0 commit comments

Comments
 (0)