Skip to content

Commit 38a1974

Browse files
author
David Miguel Susano Pinto
committed
serve_devices: make independent copies of each device conf (#274)
If devices in a list have the same conf object, a deepcopy of that list will still keep them references to each other. So iterate the list to make a separate copy for each device.
1 parent e8bdc98 commit 38a1974

File tree

2 files changed

+71
-6
lines changed

2 files changed

+71
-6
lines changed

microscope/device_server.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,12 +397,6 @@ def run(self):
397397

398398

399399
def serve_devices(devices, options: DeviceServerOptions, exit_event=None):
400-
# We make changes to `devices` (would be great if we didn't had
401-
# to) so make a a copy of it because we don't want to make those
402-
# changes on the caller. See original issue on #211 and PRs #212
403-
# and #217 (most discussion happens on #212).
404-
devices = copy.deepcopy(devices)
405-
406400
root_logger = logging.getLogger()
407401

408402
log_handler = FileHandler("__MAIN__.log")
@@ -445,6 +439,13 @@ def term_func(sig, frame):
445439
# Group devices by class.
446440
by_class = {}
447441
for dev in devices:
442+
## We may change dev['conf'] later so make a copy of it (see
443+
## original issue #211 and PRs #212 and #217 - most discussion
444+
## happens on #212). And the copy must be made on 'dev' and
445+
## not 'devices' because 'devices' may have internal refs
446+
## which are kept on a deepcopy (issue #274).
447+
dev = copy.deepcopy(dev)
448+
448449
by_class[dev["cls"]] = by_class.get(dev["cls"], []) + [dev]
449450

450451
if not by_class:

microscope/testsuite/test_device_server.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,70 @@ def test_function_in_device_definition(self):
325325
self.assertEqual(dm2.n_actuators, 20)
326326

327327

328+
class TestCopyOfDeviceConf(BaseTestServeDevices):
329+
"""Ensure that device configurations are a copy."""
330+
331+
def setUp(self):
332+
## We need to test this with floating devices since they are
333+
## the case that modifies conf. The TestFloatingDevice class
334+
## needs a `uid` arg but for this test, we need use the same
335+
## conf object for both. And because this will instantiated
336+
## on a new Process, we use this multiprocessing Queue.
337+
uids_to_pick = multiprocessing.Queue()
338+
uids_to_pick.put("foo")
339+
uids_to_pick.put("bar")
340+
class FloatingDeviceX(TestFloatingDevice):
341+
def __init__(self, **kwargs):
342+
super().__init__(uid=uids_to_pick.get(), **kwargs)
343+
344+
self.conf = {}
345+
346+
self.DEVICES = [
347+
microscope.device_server.device(
348+
FloatingDeviceX, "127.0.0.1", 8001, self.conf, uid="foo"
349+
),
350+
microscope.device_server.device(
351+
FloatingDeviceX, "127.0.0.1", 8002, self.conf, uid="bar"
352+
),
353+
]
354+
super().setUp()
355+
356+
def test_conf_copy(self):
357+
"""Test that device_server does not change the original conf.
358+
359+
Because we are dealing with a FloatingDevice, serve_devices
360+
will be injecting an 'index' on it. This test confirms that
361+
the change is on a copy of it. See issue #211 and PRs #212
362+
and #217 (most discussion happens on #212).
363+
"""
364+
self.assertFalse(
365+
"index" in self.conf,
366+
"injected 'index' key found on original - conf not copied"
367+
)
368+
369+
def test_independent_conf_copies(self):
370+
"""Test that device_server makes a copy for each device.
371+
372+
It is possible that two devices have the same conf object.
373+
Because we may modify that "conf" internally, we need to
374+
ensure that each device has its own copy of the conf.
375+
376+
This is a regression test for issue #274.
377+
378+
"""
379+
d1 = Pyro4.Proxy("PYRO:FloatingDeviceX@127.0.0.1:8001")
380+
d2 = Pyro4.Proxy("PYRO:FloatingDeviceX@127.0.0.1:8002")
381+
## These are floating devices so each gets assigned an index
382+
## by `serve_devices`. If their index are the same it means
383+
## that the device confs are references to the same dict
384+
## (probably because they were not copied properly).
385+
self.assertNotEqual(
386+
d1.get_index(),
387+
d2.get_index(),
388+
"both devices assigned the same index (sharing the same conf?)"
389+
)
390+
391+
328392
class TestKeepDeviceServerAlive(BaseTestServeDevices):
329393
DEVICES = [
330394
microscope.device_server.device(

0 commit comments

Comments
 (0)