diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c6635..f9796e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ The rules for this file: - Replaced connect error alert with dialog (PR #85) - Added refresh frequency config option for 3D view (PR #86) - Added more client side widget input validations (PR #87) +- Added configurable UI and kernel timeouts (PR #90) ### Fixed diff --git a/mdadash/backend/kernel/manager.py b/mdadash/backend/kernel/manager.py index 4c66784..4d1ab6d 100644 --- a/mdadash/backend/kernel/manager.py +++ b/mdadash/backend/kernel/manager.py @@ -279,7 +279,7 @@ async def send_message(self, msg_type: str, data: dict) -> None: self.kc.shell_channel.send(data_msg) async def send_message_await_response( - self, msg_type: str, data: dict | None = None, timeout: int = 5 + self, msg_type: str, data: dict | None = None, timeout: int | None = None ) -> dict | None: """Send message to kernel and wait for a response (async) @@ -288,11 +288,12 @@ async def send_message_await_response( msg_type: str A message type string that the kernel has a handler registered for - data: dict + data: dict | None Dict that gets passed to the handler in the kernel (default: None) - timeout: int - Timeout in seconds (default: 5) + timeout: int | None + Timeout in seconds (default: None) + When None, the configured kernel timeout in dashboard configurtion is used. Returns ------- @@ -318,6 +319,8 @@ async def send_message_await_response( future = asyncio.get_running_loop().create_future() self._pending_futures[msg_id] = future self.kc.shell_channel.send(data_msg) + if timeout is None: + timeout = self.sm.dashboard_config["kernel_timeout"] try: return await asyncio.wait_for(future, timeout=timeout) except TimeoutError as e: # pragma: no cover @@ -325,7 +328,7 @@ async def send_message_await_response( finally: self._pending_futures.pop(msg_id, None) - async def execute_code(self, code: str, timeout: int = 5) -> str: + async def execute_code(self, code: str, timeout: int | None = None) -> str: """Execute code in the kernel Parameters @@ -333,8 +336,9 @@ async def execute_code(self, code: str, timeout: int = 5) -> str: code: str Code to execute in the kernel - timeout: int - Timeout in seconds (default: 5) + timeout: int | None + Timeout in seconds (default: None) + When None, the configured kernel timeout in dashboard configurtion is used. Returns ------- @@ -342,6 +346,8 @@ async def execute_code(self, code: str, timeout: int = 5) -> str: A string representation of the output of the code executed """ + if timeout is None: + timeout = self.sm.dashboard_config["kernel_timeout"] response = await self.send_message_await_response( "execute_code", {"code": code}, timeout ) @@ -363,10 +369,12 @@ async def connect_to_simulations(self) -> dict: """ try: + # Use a larger, but configurable timeout to allow creation of Universes + # Eg: guess_bonds=True could take a while to complete response = await self.send_message_await_response( "connect_to_simulations", self.sm.universe_configs, - timeout=20, + timeout=(6 * self.sm.dashboard_config["kernel_timeout"]), ) if response["status"] == "ok": self.sm.running_state["connected"] = True diff --git a/mdadash/backend/state/manager.py b/mdadash/backend/state/manager.py index 6462ca9..0ed591b 100644 --- a/mdadash/backend/state/manager.py +++ b/mdadash/backend/state/manager.py @@ -134,7 +134,7 @@ def load(self): "nojump": False, "socket_bufsize": None, "buffer_size": 10000000, - "timeout": 5, + "timeout": 600, "continue_after_disconnect": None, "step": 1, "total_steps": None, @@ -168,8 +168,11 @@ def _upgrade(self): "selection_error": "Please enter a selection phrase", } settings = self._state["settings"] - if "view3d_frequency" not in settings["dashboard_config"]: - settings["dashboard_config"]["view3d_frequency"] = 1 + dashboard_config = settings["dashboard_config"] + if "view3d_frequency" not in dashboard_config: + dashboard_config["view3d_frequency"] = 1 + if "kernel_timeout" not in dashboard_config: + dashboard_config["kernel_timeout"] = 5 @property def state(self) -> dict: diff --git a/mdadash/frontend/src/__tests__/views/SettingsView.spec.js b/mdadash/frontend/src/__tests__/views/SettingsView.spec.js index 101f305..e937ffb 100644 --- a/mdadash/frontend/src/__tests__/views/SettingsView.spec.js +++ b/mdadash/frontend/src/__tests__/views/SettingsView.spec.js @@ -13,6 +13,8 @@ const settings = ref({ dashboard_config: { show_session_info: true, show_energies: true, + ui_request_timeout: 5, + kernel_timeout: 5, n_jobs: 2, view3d_frequency: 1, }, @@ -75,14 +77,24 @@ describe('SettingsView.vue', () => { await input.setValue(dataMap[name]) } } - // update n_jobs + // update ui_request_timeout const inputs = form.findAllComponents({ name: 'VNumberInput' }) - const n_jobs = inputs[0] + const ui_request_timeout = inputs[0] + expect(ui_request_timeout).toBeDefined() + expect(ui_request_timeout.props('rules')[0](null)).toMatch(/^Cannot be empty/) + expect(ui_request_timeout.props('rules')[0](5)).toBe(true) + // update kernel_timeout + const kernel_timeout = inputs[1] + expect(kernel_timeout).toBeDefined() + expect(kernel_timeout.props('rules')[0](null)).toMatch(/^Cannot be empty/) + expect(kernel_timeout.props('rules')[0](5)).toBe(true) + // update n_jobs + const n_jobs = inputs[2] expect(n_jobs).toBeDefined() expect(n_jobs.props('rules')[0](null)).toMatch(/^Cannot be empty/) expect(n_jobs.props('rules')[0](2)).toBe(true) // update view3d_frequency - const view3d_frequency = inputs[1] + const view3d_frequency = inputs[3] expect(view3d_frequency).toBeDefined() expect(view3d_frequency.props('rules')[0](null)).toMatch(/^Cannot be empty/) expect(view3d_frequency.props('rules')[0](1)).toBe(true) diff --git a/mdadash/frontend/src/views/SettingsView.vue b/mdadash/frontend/src/views/SettingsView.vue index 66e16f2..030e877 100644 --- a/mdadash/frontend/src/views/SettingsView.vue +++ b/mdadash/frontend/src/views/SettingsView.vue @@ -45,13 +45,41 @@ + + + + @@ -142,7 +170,7 @@ :min="8192" :max="262144" :step="1024" - hint="Size of the socket buffer in bytes. Default is to use the system default" + hint="Size of imdclient socket buffer in bytes [System default]" persistent-hint > @@ -153,7 +181,7 @@ v-model="settings.universe_configs[0].buffer_size" control-variant="hidden" :min="1000000" - hint="IMDFrameBuffer will be filled with as many IMDFrame fit in buffer_size bytes [10MB]" + hint="Size of imdclient frame buffer in bytes [1000000]" persistent-hint :rules="[(v) => !!v || 'Cannot be empty. Default value is 10000000']" validate-on="eager" @@ -165,12 +193,12 @@ variant="outlined" v-model="settings.universe_configs[0].timeout" control-variant="default" - hint="Timeout for the socket in seconds [5]" + hint="Timeout for imdclient socket in seconds [600]" persistent-hint :min="0" - :max="600" + :max="900" :step="1" - :rules="[(v) => !!v || 'Cannot be empty. Default value is 5']" + :rules="[(v) => !!v || 'Cannot be empty. Default value is 600']" validate-on="eager" > @@ -183,7 +211,7 @@ { title: 'False', value: false }, { title: 'None', value: null }, ]" - hint="Continue simulation after disconnect" + hint="Continue simulation after disconnect [None]" persistent-hint > @@ -194,7 +222,7 @@ v-model="settings.universe_configs[0].step" :min="1" control-variant="default" - hint="frame(s) during iteration" + hint="Step n frame(s) during iteration [1]" persistent-hint :rules="[(v) => !!v || 'Cannot be empty. Default value is 1']" validate-on="eager"