From fcb8410e5ee485d562923b2c53d26552724f12dd Mon Sep 17 00:00:00 2001 From: igor-shavrin <63294085+igor-shavrin@users.noreply.github.com> Date: Tue, 15 Feb 2022 18:12:12 +0100 Subject: [PATCH] fix improper cleanup on client disconnection (#9) * fix improper cleanup on client disconnection * more spell corrections --- README.md | 6 +-- TimeTaggerRPC/__init__.py | 2 +- TimeTaggerRPC/server.py | 24 +++++---- doc/changelog.rst | 5 ++ doc/conf.py | 2 +- doc/cookbook.rst | 18 +++---- tests/test_remote_object_destruction.py | 8 +-- ...mote_object_destruction_context_manager.py | 53 +++++++++++++++++++ 8 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 tests/test_remote_object_destruction_context_manager.py diff --git a/README.md b/README.md index bd779f2..907f128 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. ``` diff --git a/TimeTaggerRPC/__init__.py b/TimeTaggerRPC/__init__.py index 9b1cc94..56079ad 100644 --- a/TimeTaggerRPC/__init__.py +++ b/TimeTaggerRPC/__init__.py @@ -1,4 +1,4 @@ """RPC for Swabian Instruments' Time Tagger""" -__version__ = '0.0.5' +__version__ = '0.0.6' __author__ = 'Igor Shavrin' diff --git a/TimeTaggerRPC/server.py b/TimeTaggerRPC/server.py index cce050e..7a8e7bc 100644 --- a/TimeTaggerRPC/server.py +++ b/TimeTaggerRPC/server.py @@ -29,6 +29,14 @@ 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 @@ -36,6 +44,7 @@ class TrackedResource: """ _obj: object _id: str + _pyroDaemon: Daemon def __init__(self, obj): self._obj = obj @@ -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): diff --git a/doc/changelog.rst b/doc/changelog.rst index 95ad4e7..e8c0152 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -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()`. diff --git a/doc/conf.py b/doc/conf.py index 00454af..eec8a1d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = 'Time Tagger RPC' -copyright = '2021 Swabian Instruments GmbH' +copyright = '2022 Swabian Instruments GmbH' author = 'Igor Shavrin ' diff --git a/doc/cookbook.rst b/doc/cookbook.rst index 24bdf10..cb656ae 100644 --- a/doc/cookbook.rst +++ b/doc/cookbook.rst @@ -9,8 +9,8 @@ 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 ```` 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 ```` on which the server shall listen for connections. .. code:: @@ -18,7 +18,7 @@ This is done by starting the server with ``--host`` parameter and specifying exp On the client you can connect to the server as follows: -.. code:: python +.. code-block:: python # Process 1 from TimeTaggerRPC import client @@ -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 @@ -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 @@ -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 @@ -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 @@ -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() @@ -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. diff --git a/tests/test_remote_object_destruction.py b/tests/test_remote_object_destruction.py index 1beff6a..dbfe12c 100644 --- a/tests/test_remote_object_destruction.py +++ b/tests/test_remote_object_destruction.py @@ -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) @@ -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) @@ -48,8 +48,8 @@ crate._pyroRelease() del crate -# TT.freeTimeTagger(tagger) +TT.freeTimeTagger(tagger) # del tagger print('Sleeping ...') -time.sleep(5) +time.sleep(1) diff --git a/tests/test_remote_object_destruction_context_manager.py b/tests/test_remote_object_destruction_context_manager.py new file mode 100644 index 0000000..15ae4f7 --- /dev/null +++ b/tests/test_remote_object_destruction_context_manager.py @@ -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)