DiamondLightSource / tickit

Event-based hardware simulation framework
Apache License 2.0
7 stars 0 forks source link

Inconsistent error if 'interrupt' parameter in RegexCommand is incorrect #141

Open MJGaughran opened 1 year ago

MJGaughran commented 1 year ago

If the 'interrupt' parameter is True, when it ought to be False, the tickit device has a chance of triggering an exception that is not properly handled. This error will cause all future scheduled updates to fail to run.

I have tried to produce a minimum working example that raises the exception in question.

I have found the input / output field necessary for any tickit device; I am not sure why. It's called 'flux' as it was copied from an example.

Run the simulation: python -m tickit all ./temp_controller.yaml

On a separate terminal, send the multiple requests: ./test_comms.py

Problematic code

```python @RegexCommand(r"T\?", True, "utf-8") # Causes issues @RegexCommand(r"T\?", False, "utf-8") # Works fine ```

Error messages

Benign messages: ``` DEBUG:tickit.core.management.schedulers.base:Scheduler (MasterScheduler) got Interrupt(source='tempcont') DEBUG:tickit.core.management.schedulers.base:Scheduling tempcont for wakeup at 14296887047 ``` Main error: ``` Task exception: Traceback (most recent call last): File "/venv/lib/python3.10/site-packages/tickit/core/runner.py", line 21, in run_with_error_handling await awaitable File "/venv/lib/python3.10/site-packages/tickit/core/management/schedulers/master.py", line 72, in run_forever await self._do_tick() File "/venv/lib/python3.10/site-packages/tickit/core/management/schedulers/master.py", line 88, in _do_tick assert when is not None AssertionError ```

device.py

```python from dataclasses import dataclass from tickit.adapters.composed import ComposedAdapter from tickit.adapters.interpreters.command import CommandInterpreter from tickit.adapters.interpreters.command.regex_command import RegexCommand from tickit.adapters.servers.tcp import ByteFormat, TcpServer from tickit.core.components.component import Component, ComponentConfig from tickit.core.components.device_simulation import DeviceSimulation from tickit.core.device import Device, DeviceUpdate from tickit.core.typedefs import SimTime from typing_extensions import TypedDict class TempControllerDevice(Device): Inputs: type = TypedDict("Inputs", {"flux": float}) Outputs: type = TypedDict("Outputs", {"flux": float}) def __init__( self, ) -> None: self._temp = 10.0 def get_temp(self): return self._temp def update(self, time: SimTime, inputs: Inputs) -> DeviceUpdate[Outputs]: return DeviceUpdate(TempControllerDevice.Outputs(flux=inputs["flux"]), None) class TempControllerAdapter(ComposedAdapter): device: TempControllerDevice def __init__( self, host: str = "localhost", port: int = 25565, ) -> None: super().__init__( TcpServer(host, port, ByteFormat(b"%b\r\n")), CommandInterpreter(), ) @RegexCommand(r"T\?", True, "utf-8") async def get_temperature(self) -> bytes: return str(self.device.get_temp()).encode("utf-8") @dataclass class TempController(ComponentConfig): host: str = "localhost" port: int = 25565 def __call__(self) -> Component: return DeviceSimulation( name=self.name, device=TempControllerDevice(), adapters=[TempControllerAdapter(host=self.host, port=self.port)], ) ```

temp_controller.yaml

``` - tickit.devices.source.Source: name: source inputs: {} value: 66.0 - device.TempController: name: tempcont inputs: flux: source:value - tickit.devices.sink.Sink: name: sink inputs: flux: tempcont:flux ```

test_comms.py

```python #! /bin/env python import socket HOSTNAME = "localhost" PORT = 25565 COUNT = 10 def send_message(s): message = b"T?\r\n" s.sendall(message) print(f"Sent: {message}") response = s.recv(1024) print(f"Received: {response}") def send_commands(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOSTNAME, PORT)) for _ in range(COUNT): send_message(s) s.close() if __name__ == "__main__": send_commands() ```