forked from M-Labs/artiq
pc_rpc: raise AttributeError immediately for nonexistent RPC methods. Closes #534
This commit is contained in:
parent
f010a74479
commit
c7c8ad126f
|
@ -10,6 +10,9 @@ Release notes
|
||||||
ARTIQ_APPLET_EMBED. The GUI sets this enviroment variable itself and the
|
ARTIQ_APPLET_EMBED. The GUI sets this enviroment variable itself and the
|
||||||
user simply needs to remove the --embed argument.
|
user simply needs to remove the --embed argument.
|
||||||
* EnvExperiment's prepare calls prepare for all its children.
|
* EnvExperiment's prepare calls prepare for all its children.
|
||||||
|
* Dynamic __getattr__'s returning RPC target methods are not supported anymore.
|
||||||
|
Controller driver classes must define all their methods intended for RPC as
|
||||||
|
members.
|
||||||
|
|
||||||
|
|
||||||
2.0rc1
|
2.0rc1
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Controller:
|
||||||
await remote.connect_rpc(self.host, self.port, None)
|
await remote.connect_rpc(self.host, self.port, None)
|
||||||
try:
|
try:
|
||||||
targets, _ = remote.get_rpc_id()
|
targets, _ = remote.get_rpc_id()
|
||||||
remote.select_rpc_target(targets[0])
|
await remote.select_rpc_target(targets[0])
|
||||||
r = await getattr(remote, method)(*args, **kwargs)
|
r = await getattr(remote, method)(*args, **kwargs)
|
||||||
finally:
|
finally:
|
||||||
remote.close_rpc()
|
remote.close_rpc()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -20,6 +21,9 @@ class FFProxy:
|
||||||
"""
|
"""
|
||||||
def __init__(self, target):
|
def __init__(self, target):
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
|
valid_methods = inspect.getmembers(target, inspect.ismethod)
|
||||||
|
self._valid_methods = {m[0] for m in valid_methods}
|
||||||
self._thread = None
|
self._thread = None
|
||||||
|
|
||||||
def ff_join(self):
|
def ff_join(self):
|
||||||
|
@ -28,6 +32,8 @@ class FFProxy:
|
||||||
self._thread.join()
|
self._thread.join()
|
||||||
|
|
||||||
def __getattr__(self, k):
|
def __getattr__(self, k):
|
||||||
|
if k not in self._valid_methods:
|
||||||
|
raise AttributeError
|
||||||
def run_in_thread(*args, **kwargs):
|
def run_in_thread(*args, **kwargs):
|
||||||
if self._thread is not None and self._thread.is_alive():
|
if self._thread is not None and self._thread.is_alive():
|
||||||
logger.warning("skipping fire-and-forget call to %r.%s as "
|
logger.warning("skipping fire-and-forget call to %r.%s as "
|
||||||
|
|
|
@ -94,8 +94,6 @@ class Client:
|
||||||
in the middle of a RPC can break subsequent RPCs (from the same
|
in the middle of a RPC can break subsequent RPCs (from the same
|
||||||
client).
|
client).
|
||||||
"""
|
"""
|
||||||
kernel_invariants = set()
|
|
||||||
|
|
||||||
def __init__(self, host, port, target_name=AutoTarget, timeout=None):
|
def __init__(self, host, port, target_name=AutoTarget, timeout=None):
|
||||||
self.__socket = socket.create_connection((host, port), timeout)
|
self.__socket = socket.create_connection((host, port), timeout)
|
||||||
|
|
||||||
|
@ -106,6 +104,7 @@ class Client:
|
||||||
self.__target_names = server_identification["targets"]
|
self.__target_names = server_identification["targets"]
|
||||||
self.__description = server_identification["description"]
|
self.__description = server_identification["description"]
|
||||||
self.__selected_target = None
|
self.__selected_target = None
|
||||||
|
self.__valid_methods = set()
|
||||||
if target_name is not None:
|
if target_name is not None:
|
||||||
self.select_rpc_target(target_name)
|
self.select_rpc_target(target_name)
|
||||||
except:
|
except:
|
||||||
|
@ -118,6 +117,7 @@ class Client:
|
||||||
target_name = _validate_target_name(target_name, self.__target_names)
|
target_name = _validate_target_name(target_name, self.__target_names)
|
||||||
self.__socket.sendall((target_name + "\n").encode())
|
self.__socket.sendall((target_name + "\n").encode())
|
||||||
self.__selected_target = target_name
|
self.__selected_target = target_name
|
||||||
|
self.__valid_methods = self.__recv()
|
||||||
|
|
||||||
def get_selected_target(self):
|
def get_selected_target(self):
|
||||||
"""Returns the selected target, or ``None`` if no target has been
|
"""Returns the selected target, or ``None`` if no target has been
|
||||||
|
@ -173,6 +173,8 @@ class Client:
|
||||||
return self.__do_action(obj)
|
return self.__do_action(obj)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
if name not in self.__valid_methods:
|
||||||
|
raise AttributeError
|
||||||
def proxy(*args, **kwargs):
|
def proxy(*args, **kwargs):
|
||||||
return self.__do_rpc(name, args, kwargs)
|
return self.__do_rpc(name, args, kwargs)
|
||||||
return proxy
|
return proxy
|
||||||
|
@ -187,8 +189,6 @@ class AsyncioClient:
|
||||||
Concurrent access from different asyncio tasks is supported; all calls
|
Concurrent access from different asyncio tasks is supported; all calls
|
||||||
use a single lock.
|
use a single lock.
|
||||||
"""
|
"""
|
||||||
kernel_invariants = set()
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__lock = asyncio.Lock()
|
self.__lock = asyncio.Lock()
|
||||||
self.__reader = None
|
self.__reader = None
|
||||||
|
@ -208,19 +208,21 @@ class AsyncioClient:
|
||||||
self.__target_names = server_identification["targets"]
|
self.__target_names = server_identification["targets"]
|
||||||
self.__description = server_identification["description"]
|
self.__description = server_identification["description"]
|
||||||
self.__selected_target = None
|
self.__selected_target = None
|
||||||
|
self.__valid_methods = set()
|
||||||
if target_name is not None:
|
if target_name is not None:
|
||||||
self.select_rpc_target(target_name)
|
await self.select_rpc_target(target_name)
|
||||||
except:
|
except:
|
||||||
self.close_rpc()
|
self.close_rpc()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def select_rpc_target(self, target_name):
|
async def select_rpc_target(self, target_name):
|
||||||
"""Selects a RPC target by name. This function should be called
|
"""Selects a RPC target by name. This function should be called
|
||||||
exactly once if the connection was created with ``target_name=None``.
|
exactly once if the connection was created with ``target_name=None``.
|
||||||
"""
|
"""
|
||||||
target_name = _validate_target_name(target_name, self.__target_names)
|
target_name = _validate_target_name(target_name, self.__target_names)
|
||||||
self.__writer.write((target_name + "\n").encode())
|
self.__writer.write((target_name + "\n").encode())
|
||||||
self.__selected_target = target_name
|
self.__selected_target = target_name
|
||||||
|
self.__valid_methods = await self.__recv()
|
||||||
|
|
||||||
def get_selected_target(self):
|
def get_selected_target(self):
|
||||||
"""Returns the selected target, or ``None`` if no target has been
|
"""Returns the selected target, or ``None`` if no target has been
|
||||||
|
@ -273,6 +275,8 @@ class AsyncioClient:
|
||||||
self.__lock.release()
|
self.__lock.release()
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
if name not in self.__valid_methods:
|
||||||
|
raise AttributeError
|
||||||
async def proxy(*args, **kwargs):
|
async def proxy(*args, **kwargs):
|
||||||
res = await self.__do_rpc(name, args, kwargs)
|
res = await self.__do_rpc(name, args, kwargs)
|
||||||
return res
|
return res
|
||||||
|
@ -292,8 +296,6 @@ class BestEffortClient:
|
||||||
:param retry: Amount of time to wait between retries when reconnecting
|
:param retry: Amount of time to wait between retries when reconnecting
|
||||||
in the background.
|
in the background.
|
||||||
"""
|
"""
|
||||||
kernel_invariants = set()
|
|
||||||
|
|
||||||
def __init__(self, host, port, target_name,
|
def __init__(self, host, port, target_name,
|
||||||
firstcon_timeout=1.0, retry=5.0):
|
firstcon_timeout=1.0, retry=5.0):
|
||||||
self.__host = host
|
self.__host = host
|
||||||
|
@ -303,6 +305,7 @@ class BestEffortClient:
|
||||||
|
|
||||||
self.__conretry_terminate = False
|
self.__conretry_terminate = False
|
||||||
self.__socket = None
|
self.__socket = None
|
||||||
|
self.__valid_methods = set()
|
||||||
try:
|
try:
|
||||||
self.__coninit(firstcon_timeout)
|
self.__coninit(firstcon_timeout)
|
||||||
except:
|
except:
|
||||||
|
@ -327,6 +330,7 @@ class BestEffortClient:
|
||||||
target_name = _validate_target_name(self.__target_name,
|
target_name = _validate_target_name(self.__target_name,
|
||||||
server_identification["targets"])
|
server_identification["targets"])
|
||||||
self.__socket.sendall((target_name + "\n").encode())
|
self.__socket.sendall((target_name + "\n").encode())
|
||||||
|
self.__valid_methods = self.__recv()
|
||||||
|
|
||||||
def __start_conretry(self):
|
def __start_conretry(self):
|
||||||
self.__conretry_thread = threading.Thread(target=self.__conretry)
|
self.__conretry_thread = threading.Thread(target=self.__conretry)
|
||||||
|
@ -401,6 +405,8 @@ class BestEffortClient:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
if name not in self.__valid_methods:
|
||||||
|
raise AttributeError
|
||||||
def proxy(*args, **kwargs):
|
def proxy(*args, **kwargs):
|
||||||
return self.__do_rpc(name, args, kwargs)
|
return self.__do_rpc(name, args, kwargs)
|
||||||
return proxy
|
return proxy
|
||||||
|
@ -558,6 +564,12 @@ class Server(_AsyncioServer):
|
||||||
if callable(target):
|
if callable(target):
|
||||||
target = target()
|
target = target()
|
||||||
|
|
||||||
|
valid_methods = inspect.getmembers(target, inspect.ismethod)
|
||||||
|
valid_methods = {m[0] for m in valid_methods}
|
||||||
|
if self.builtin_terminate:
|
||||||
|
valid_methods.add("terminate")
|
||||||
|
writer.write((pyon.encode(valid_methods) + "\n").encode())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
line = await reader.readline()
|
line = await reader.readline()
|
||||||
if not line:
|
if not line:
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ControllerCase(unittest.TestCase):
|
||||||
remote = AsyncioClient()
|
remote = AsyncioClient()
|
||||||
await remote.connect_rpc(host, port, None)
|
await remote.connect_rpc(host, port, None)
|
||||||
targets, _ = remote.get_rpc_id()
|
targets, _ = remote.get_rpc_id()
|
||||||
remote.select_rpc_target(targets[0])
|
await remote.select_rpc_target(targets[0])
|
||||||
self.addCleanup(remote.close_rpc)
|
self.addCleanup(remote.close_rpc)
|
||||||
return remote
|
return remote
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class RPCCase(unittest.TestCase):
|
||||||
test_object_back = remote.async_echo(test_object)
|
test_object_back = remote.async_echo(test_object)
|
||||||
self.assertEqual(test_object, test_object_back)
|
self.assertEqual(test_object, test_object_back)
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
remote.non_existing_method()
|
remote.non_existing_method
|
||||||
remote.terminate()
|
remote.terminate()
|
||||||
finally:
|
finally:
|
||||||
remote.close_rpc()
|
remote.close_rpc()
|
||||||
|
@ -73,7 +73,7 @@ class RPCCase(unittest.TestCase):
|
||||||
test_object_back = await remote.async_echo(test_object)
|
test_object_back = await remote.async_echo(test_object)
|
||||||
self.assertEqual(test_object, test_object_back)
|
self.assertEqual(test_object, test_object_back)
|
||||||
with self.assertRaises(AttributeError):
|
with self.assertRaises(AttributeError):
|
||||||
await remote.non_existing_method()
|
await remote.non_existing_method
|
||||||
await remote.terminate()
|
await remote.terminate()
|
||||||
finally:
|
finally:
|
||||||
remote.close_rpc()
|
remote.close_rpc()
|
||||||
|
@ -101,6 +101,8 @@ class FireAndForgetCase(unittest.TestCase):
|
||||||
self.ok = False
|
self.ok = False
|
||||||
p = fire_and_forget.FFProxy(self)
|
p = fire_and_forget.FFProxy(self)
|
||||||
p._set_ok()
|
p._set_ok()
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
p.non_existing_method
|
||||||
p.ff_join()
|
p.ff_join()
|
||||||
self.assertTrue(self.ok)
|
self.assertTrue(self.ok)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue