Skip to content

Commit

Permalink
fix improper cleanup on client disconnection (#9)
Browse files Browse the repository at this point in the history
* fix improper cleanup on client disconnection

* more spell corrections
  • Loading branch information
igor-shavrin authored Feb 15, 2022
1 parent 3246742 commit fcb8410
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Time Tagger RPC implementation using [Pyro5](https://pypi.org/project/Pyro5/).
### Alpha version !
This project is in the alpha stage of the development. This means that the code
successfully passed basic testing and is operational.
However, some things might be broken and the API may change in the future versions.
However, some things might be broken, and the API may change in the future versions.


### Install
Expand All @@ -33,8 +33,8 @@ optional arguments:
-h, --help show this help message and exit
--host localhost Hostname or IP on which the server will listen for connections.
--port 23000 Server port.
--use_ns Use Pyro5 nameserver.
--start_ns Start Pyro5 nameserver in a subprocess.
--use_ns Use Pyro5 name server.
--start_ns Start Pyro5 name server in a subprocess.
```


Expand Down
2 changes: 1 addition & 1 deletion TimeTaggerRPC/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""RPC for Swabian Instruments' Time Tagger"""

__version__ = '0.0.5'
__version__ = '0.0.6'
__author__ = 'Igor Shavrin'
24 changes: 14 additions & 10 deletions TimeTaggerRPC/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,22 @@
logger = logging.getLogger('TimeTaggerRPC.server')


class Daemon(Pyro5.api.Daemon):
"""Customized Pyro5 Daemon."""

def proxy2object(self, pyro_proxy):
"""Returns the Pyro object for a given proxy."""
objectId = pyro_proxy._pyroUri.object
return self.objectsById.get(objectId)

class TrackedResource:
"""Implements 'close' method that clears the underlying object.
This class is not exposed by the Pyro and therefore its methods do
not appear on the client proxy.
"""
_obj: object
_id: str
_pyroDaemon: Daemon

def __init__(self, obj):
self._obj = obj
Expand All @@ -56,18 +65,13 @@ def close(self):
self._logger.debug(e)
finally:
self._obj = None
Pyro5.api.current_context.untrack_resource(self)
# Unregister the Pyro object.
# Tracking will be removed automatically.
if hasattr(self, '_pyroDaemon'):
self._pyroDaemon.unregister(self)


class Daemon(Pyro5.api.Daemon):
"""Customized Pyro5 Daemon."""

def proxy2object(self, pyro_proxy):
"""Returns the Pyro object for a given proxy."""
objectId = pyro_proxy._pyroUri.object
return self.objectsById.get(objectId)
self._logger.debug('Unregistered: %s', self)
else:
self._logger.warning('Failed to unregister: %s', self)


def make_module_function_proxy(func_name: str):
Expand Down
5 changes: 5 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Changelog
##############

v 0.0.6 - 2022-02-15
====================
* Fixed improper cleanup on client disconnection that resulted in growing server memory and CPU usage.


v 0.0.5 - 2022-01-03
====================
* Added support for iterator methods :meth:`ttdoc:Counter.getDataObject()`.
Expand Down
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# -- Project information -----------------------------------------------------

project = 'Time Tagger RPC'
copyright = '2021 Swabian Instruments GmbH'
copyright = '2022 Swabian Instruments GmbH'
author = 'Igor Shavrin <[email protected]>'


Expand Down
18 changes: 9 additions & 9 deletions doc/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ Serving Time Tagger to multiple clients

This section describes how to access the same Time Tagger object from multiple processes on one PC.

In order to access the TimeTaggerRPC server from multiple PCs, you have to configure the server for providing access over the network.
This is done by starting the server with ``--host`` parameter and specifying explicit IP address ``<SERVER_IP>`` on which the server shall listen for connections.
For accessing the TimeTaggerRPC server from a different PC, you have to instruct the server to access over the network.
You have to start the server with ``--host`` parameter and specifying explicit IP address ``<SERVER_IP>`` on which the server shall listen for connections.

.. code::
TimeTaggerRPC-server --host <SERVER_IP>
On the client you can connect to the server as follows:

.. code:: python
.. code-block:: python
# Process 1
from TimeTaggerRPC import client
Expand All @@ -32,7 +32,7 @@ On the client you can connect to the server as follows:
Now you can do on another process / PC the following

.. code:: python
.. code-block:: python
# Process 2
from TimeTaggerRPC import client
Expand All @@ -58,7 +58,7 @@ This demonstrates how one can use multiple clients or processes to access the sa
All clients connected to the same object have full control over it.
For example, If any of the clients execute ``TT.freeTimeTagger(tagger_proxy)``,
all clients using this object will be affected because the server received
a command to close the TimeTagger hardware connection.
a command to close the Time Tagger hardware connection.

Currently, there is no intention to implement access management code in the TimeTaggerRPC package.
If you want to develop a common access infrastructure in your lab then you can follow one of the strategies
Expand All @@ -70,7 +70,7 @@ If you want to develop a common access infrastructure in your lab then you can f
Multithreading and proxy objects
=================================

The TimeTagger library is multithreaded and thread-safe, this means you can safely use Time Tagger objects from multiple threads.
The Time Tagger library is multithreaded and thread-safe, this means you can safely use Time Tagger objects from multiple threads.
However it is not the same when you use TimeTaggerRPC. The distinction is that
the client code does not operate on the Time Tagger objects but on the Pyro5 proxy objects.
The proxy objects maintain the network connection to the server and identify
Expand All @@ -85,7 +85,7 @@ https://github.com/irmen/Pyro5/tree/master/examples/threadproxysharing.

The following example shows how this works.

.. code-block::
.. code-block:: python
import time
import threading
Expand Down Expand Up @@ -126,7 +126,7 @@ The following example shows how this works.
except KeyboardInterrupt:
stop_evt.set()
print('Exiting..')
print('Exiting...')
finally:
t1.join()
t2.join()
Expand Down Expand Up @@ -172,7 +172,7 @@ On the server computer
On the client computer
^^^^^^^^^^^^^^^^^^^^^^
1. Install SSH client. On many modern operating systems it is already available.
1. Install SSH client if not already available on your operating system.

2. Setup SSH local port forwarding, so all communication to a local port will be forwarded to the remote port 23000.

Expand Down
8 changes: 4 additions & 4 deletions tests/test_remote_object_destruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
hist_list.append(h)
sm.registerMeasurement(h)

for i in range(5, 10):
for i in range(5, 40):
h = TT.Correlation(sm.getTagger(), 1, DELAYED_CH, binwidth=10, n_bins=2000)
print(f'hist_{i}', h._pyroUri)
hist_list.append(h)
Expand All @@ -37,7 +37,7 @@
print('crate', crate._pyroUri)
crate.clear()

sm.startFor(int(2e12), clear=True)
sm.startFor(int(1e12), clear=True)

while sm.isRunning():
time.sleep(0.05)
Expand All @@ -48,8 +48,8 @@
crate._pyroRelease()
del crate

# TT.freeTimeTagger(tagger)
TT.freeTimeTagger(tagger)
# del tagger

print('Sleeping ...')
time.sleep(5)
time.sleep(1)
53 changes: 53 additions & 0 deletions tests/test_remote_object_destruction_context_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import time

from TimeTaggerRPC.client import createProxy

with createProxy() as TT:

print('TT', TT._pyroUri)

# Find Time Taggers available at the remote system
print('Available Time Taggers', TT.scanTimeTagger())

# Create Time Tagger
with TT.createTimeTagger() as tagger:
tagger.setTestSignal(1, True)
tagger.setTestSignal(2, True)

print('tagger', tagger._pyroUri, tagger.getSerial())

delayed_vch = TT.DelayedChannel(tagger, 2, 1000)
DELAYED_CH = delayed_vch.getChannel()

with TT.SynchronizedMeasurements(tagger) as sm:

hist_list = list()
for i in range(5):
h = TT.Correlation(tagger, 1, DELAYED_CH, binwidth=10, n_bins=2000)
print(f'hist_{i}', h._pyroUri)
hist_list.append(h)
sm.registerMeasurement(h)

for i in range(5, 40):
h = TT.Correlation(sm.getTagger(), 1, DELAYED_CH, binwidth=10, n_bins=2000)
print(f'hist_{i}', h._pyroUri)
hist_list.append(h)

crate = TT.Countrate(tagger, [1, 2])
print('crate', crate._pyroUri)
crate.clear()

sm.startFor(int(1e12), clear=True)

while sm.isRunning():
time.sleep(0.05)

for h in hist_list:
h._pyroRelease()

print('Countrates', crate.getData())



print('Sleeping ...')
time.sleep(1)

0 comments on commit fcb8410

Please sign in to comment.