From 6cc710d3a737ec5f61514d22140d406be26662a1 Mon Sep 17 00:00:00 2001 From: aranega Date: Tue, 7 Nov 2023 05:32:43 -0600 Subject: [PATCH 01/14] Add mechanism to be able to react on kernel status change from the frontend --- webapp/components/NetPyNE.js | 6 ++- .../general/GeppettoJupyterUtils.js | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/webapp/components/NetPyNE.js b/webapp/components/NetPyNE.js index 0826b593..54993ca1 100644 --- a/webapp/components/NetPyNE.js +++ b/webapp/components/NetPyNE.js @@ -66,7 +66,7 @@ class NetPyNE extends React.Component { return; } this.loaded = true; - console.log('Netpyne is ready'); + console.log('NetPyNE-UI component is ready'); if (window !== window.parent) { window.parent.postMessage({ type: 'APP_READY', @@ -85,6 +85,10 @@ class NetPyNE extends React.Component { // A message from the parent frame can specify the file to load window.addEventListener('message', loadFromEvent); // window.load = loadFromEvent + const logme = (event) => { + console.log("!!!! EVENT", event) + } + window.addEventListener('kernelstatus', logme) } componentWillUnmount () { diff --git a/webapp/components/general/GeppettoJupyterUtils.js b/webapp/components/general/GeppettoJupyterUtils.js index 71d8956f..0022d27f 100644 --- a/webapp/components/general/GeppettoJupyterUtils.js +++ b/webapp/components/general/GeppettoJupyterUtils.js @@ -1,3 +1,41 @@ +const registerKernelListeners = () => { + try { + if(IPython.notebook.kernel == null) { + console.warn("Kernel not initialized. Waiting to register kernel event listeners"); + setTimeout(registerKernelListeners, 500); + return; + } + } catch (error) { + console.warn("IPython not initialized. Waiting to register kernel event listeners"); + setTimeout(registerKernelListeners, 500); + return + } + + const notebook = IPython.notebook; + const handleKernelStatusChange = (event, data) => { + const kernelStatusEvent = new CustomEvent("kernelstatus", { + detail: { + "type": event.type, + ...data + }, + }); + window.dispatchEvent(kernelStatusEvent); + }; + + notebook.events.on('kernel_created.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_reconnecting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_connected.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_starting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_restarting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_autorestarting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_interrupting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_disconnected.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_ready.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_killed.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_dead.Kernel', handleKernelStatusChange); +} +registerKernelListeners(); + const handle_output = function (data) { // data is the object passed to the callback from the kernel execution switch (data.msg_type) { From 0b051972c308af257467ef5c2066e142e24d768b Mon Sep 17 00:00:00 2001 From: aranega Date: Wed, 8 Nov 2023 04:52:58 -0600 Subject: [PATCH 02/14] netpyne-44 Create dedicated store for commands --- webapp/redux/actiondomainStore.js | 38 ++++++++++++++++++++++++++++ webapp/redux/actions/actiondomain.js | 19 ++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 webapp/redux/actiondomainStore.js create mode 100644 webapp/redux/actions/actiondomain.js diff --git a/webapp/redux/actiondomainStore.js b/webapp/redux/actiondomainStore.js new file mode 100644 index 00000000..0d3d400e --- /dev/null +++ b/webapp/redux/actiondomainStore.js @@ -0,0 +1,38 @@ +// import action types +import { RECORD_COMMAND, REPLAY_COMMANDS } from "./actions/actiondomain"; + +// Default state for general +export const ACTION_DOMAIN_DEFAULT_STATE = { + +}; + +const recordCommand = (state, { kernel, command }) => { + const newState = { ...state }; + if (!newState[kernel]) { + newState[kernel] = [] + } + newState[kernel].push(command) + return newState; +} + +const replayCommands = (state, { kernel }) => { + console.log("TODO, REPLAY COMMANDS") +} + +// reducer +const reducer = (state = ACTION_DOMAIN_DEFAULT_STATE, action) => { + switch (action.type) { + case RECORD_COMMAND: + return recordCommand(state, action.payload) + case REPLAY_COMMANDS: + return replayCommands(state, action.payload) + default: + return state + } +} + +const redux = require("redux"); +const createStore = redux.createStore; +const store = createStore(reducer); + +export { store } \ No newline at end of file diff --git a/webapp/redux/actions/actiondomain.js b/webapp/redux/actions/actiondomain.js new file mode 100644 index 00000000..0afeb40d --- /dev/null +++ b/webapp/redux/actions/actiondomain.js @@ -0,0 +1,19 @@ +// Action Types +export const RECORD_COMMAND = 'RECORD_COMMAND'; +export const REPLAY_COMMANDS = 'REPLAY_COMMANDS'; + +// Actions +export const recordCommand = (kernelID, command) => ({ + type: RECORD_COMMAND, + payload: { + kernel: kernelID, + command: command + } +}); + +export const replayCommands = (kernelID) => ({ + type: REPLAY_COMMANDS, + payload: { + kernel: kernelID + } +}) \ No newline at end of file From d437e86d63f68cf3927987bea98322f1151354c5 Mon Sep 17 00:00:00 2001 From: aranega Date: Wed, 8 Nov 2023 04:53:24 -0600 Subject: [PATCH 03/14] netpyne-44 Add first implementation of a replay system --- webapp/components/NetPyNE.js | 30 +++++++- webapp/components/general/CommandRecorder.js | 75 +++++++++++++++++++ .../general/GeppettoJupyterUtils.js | 62 ++++++--------- 3 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 webapp/components/general/CommandRecorder.js diff --git a/webapp/components/NetPyNE.js b/webapp/components/NetPyNE.js index 54993ca1..2269af15 100644 --- a/webapp/components/NetPyNE.js +++ b/webapp/components/NetPyNE.js @@ -13,6 +13,8 @@ import { TutorialObserver } from 'netpyne/components'; import { loadModel } from '../redux/actions/general'; +import { execPythonMessage } from './general/GeppettoJupyterUtils'; +import { replayAll } from './general/CommandRecorder'; const styles = ({ zIndex }) => ({ root: { @@ -46,6 +48,10 @@ class NetPyNE extends React.Component { super(props); this.openPythonCallDialog = this.openPythonCallDialog.bind(this); this.loaded = false; + this.kernelRestartState = { + state: "idle", + kernelID: undefined + } } componentDidMount () { @@ -84,9 +90,27 @@ class NetPyNE extends React.Component { }; // A message from the parent frame can specify the file to load window.addEventListener('message', loadFromEvent); - // window.load = loadFromEvent - const logme = (event) => { - console.log("!!!! EVENT", event) + + // Logic for kernel reinit + const logme = ({ detail: { kernel, type } }) => { + console.log("!!!! EVENT", type) + switch (this.kernelRestartState.state) { + case "restarting": + if (type === "kernel_ready" || type === "kernel_autorestarting") { + replayAll(this.kernelRestartState.kernelID) + this.kernelRestartState = { + state: "idle", + kernelID: undefined + } + } + case "idle": + if (type === "kernel_restarting") { + this.kernelRestartState = { + state: "restarting", + kernelID: kernel.id + } + } + } } window.addEventListener('kernelstatus', logme) } diff --git a/webapp/components/general/CommandRecorder.js b/webapp/components/general/CommandRecorder.js new file mode 100644 index 00000000..1ef6e1d6 --- /dev/null +++ b/webapp/components/general/CommandRecorder.js @@ -0,0 +1,75 @@ +import { store } from '../../redux/actiondomainStore' +import { recordCommand } from '../../redux/actions/actiondomain'; +import { execPythonMessageWithoutRecording } from './GeppettoJupyterUtils'; + + +const registerKernelListeners = () => { + try { + if(IPython.notebook.kernel == null) { + console.warn("Kernel not initialized. Waiting to register kernel event listeners"); + setTimeout(registerKernelListeners, 500); + return; + } + } catch (error) { + console.warn("IPython not initialized. Waiting to register kernel event listeners"); + setTimeout(registerKernelListeners, 500); + return + } + + const notebook = IPython.notebook; + const handleKernelStatusChange = (event, data) => { + const kernelStatusEvent = new CustomEvent("kernelstatus", { + detail: { + "type": event.type, + ...data + }, + }); + window.dispatchEvent(kernelStatusEvent); + }; + + const handleExecutionRequest = (event, data) => { + if (data.content.netpyne_ui_triggered) { + return + } + const { kernel, content } = data; + console.log("Kernel", kernel.id, "execute", content.code) + record(kernel.id, content.code); + } + + // Kernel lifecycle requests + notebook.events.on('kernel_created.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_reconnecting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_connected.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_starting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_restarting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_autorestarting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_interrupting.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_disconnected.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_ready.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_killed.Kernel', handleKernelStatusChange); + notebook.events.on('kernel_dead.Kernel', handleKernelStatusChange); + + // Execution requests + notebook.events.on('execution_request.Kernel', handleExecutionRequest); +} +registerKernelListeners(); + + +const record = (kernelID, command) => { + store.dispatch(recordCommand(kernelID, command)) + const actionStore = store.getState(); + console.log("store", actionStore) +} + +const replayAll = (kernelID) => { + const commands = [ + "from jupyter_geppetto import jupyter_geppetto", + "from jupyter_geppetto import utils", + "from netpyne_ui.netpyne_geppetto import netpyne_geppetto", + "netpyne_geppetto.deleteModel({})", + ...store.getState()[kernelID]]; + commands.pop() // we drop the last command which is probably the faulty one + execPythonMessageWithoutRecording(commands.join('\n')) +} + +export { record, replayAll } \ No newline at end of file diff --git a/webapp/components/general/GeppettoJupyterUtils.js b/webapp/components/general/GeppettoJupyterUtils.js index 0022d27f..0b61e3fc 100644 --- a/webapp/components/general/GeppettoJupyterUtils.js +++ b/webapp/components/general/GeppettoJupyterUtils.js @@ -1,40 +1,5 @@ -const registerKernelListeners = () => { - try { - if(IPython.notebook.kernel == null) { - console.warn("Kernel not initialized. Waiting to register kernel event listeners"); - setTimeout(registerKernelListeners, 500); - return; - } - } catch (error) { - console.warn("IPython not initialized. Waiting to register kernel event listeners"); - setTimeout(registerKernelListeners, 500); - return - } +import { record as recordCommand } from './CommandRecorder'; - const notebook = IPython.notebook; - const handleKernelStatusChange = (event, data) => { - const kernelStatusEvent = new CustomEvent("kernelstatus", { - detail: { - "type": event.type, - ...data - }, - }); - window.dispatchEvent(kernelStatusEvent); - }; - - notebook.events.on('kernel_created.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_reconnecting.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_connected.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_starting.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_restarting.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_autorestarting.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_interrupting.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_disconnected.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_ready.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_killed.Kernel', handleKernelStatusChange); - notebook.events.on('kernel_dead.Kernel', handleKernelStatusChange); -} -registerKernelListeners(); const handle_output = function (data) { // data is the object passed to the callback from the kernel execution @@ -67,9 +32,24 @@ const handle_output = function (data) { } }; -const execPythonMessage = function (command, callback = handle_output) { +const execPythonMessage = function (command, callback = handle_output, record = true) { const { kernel } = IPython.notebook; - const messageID = kernel.execute(command, { iopub: { output: callback } }, { silent: false, stop_on_error: true, store_history: true }); + console.log("Kernel", kernel.id, "executes", command) + if (record) { + recordCommand(kernel.id, command) + } + const messageID = kernel.execute( + command, + { + iopub: { output: callback } + }, + { + silent: false, + stop_on_error: true, + store_history: true, + netpyne_ui_triggered: true + }); + return new Promise((resolve, reject) => GEPPETTO.on(GEPPETTO.Events.Receive_Python_Message, (data) => { if (data.data.id == messageID) { @@ -78,6 +58,10 @@ const execPythonMessage = function (command, callback = handle_output) { })); }; +const execPythonMessageWithoutRecording = function (command, callback = handle_output) { + return execPythonMessage(command, callback, false) +} + const addslashes = function (str) { return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0'); } @@ -99,4 +83,4 @@ const evalPythonMessage = function (command, parameters, parse = true) { return execPythonMessage(finalCommand, handle_output); }; -export { execPythonMessage, evalPythonMessage }; +export { execPythonMessage, evalPythonMessage, execPythonMessageWithoutRecording }; From 990314171d1a2ceb2411cc357dcd6753ad459f38 Mon Sep 17 00:00:00 2001 From: aranega Date: Wed, 8 Nov 2023 07:53:08 -0600 Subject: [PATCH 04/14] netpyne-44 Add new action to drop last command --- webapp/components/general/CommandRecorder.js | 14 ++++++----- .../general/GeppettoJupyterUtils.js | 1 - webapp/redux/actiondomainStore.js | 23 +++++++++++++++---- webapp/redux/actions/actiondomain.js | 14 ++++++++--- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/webapp/components/general/CommandRecorder.js b/webapp/components/general/CommandRecorder.js index 1ef6e1d6..5bbeef6b 100644 --- a/webapp/components/general/CommandRecorder.js +++ b/webapp/components/general/CommandRecorder.js @@ -1,5 +1,5 @@ import { store } from '../../redux/actiondomainStore' -import { recordCommand } from '../../redux/actions/actiondomain'; +import { recordCommand, dropLastCommand } from '../../redux/actions/actiondomain'; import { execPythonMessageWithoutRecording } from './GeppettoJupyterUtils'; @@ -32,7 +32,6 @@ const registerKernelListeners = () => { return } const { kernel, content } = data; - console.log("Kernel", kernel.id, "execute", content.code) record(kernel.id, content.code); } @@ -57,8 +56,6 @@ registerKernelListeners(); const record = (kernelID, command) => { store.dispatch(recordCommand(kernelID, command)) - const actionStore = store.getState(); - console.log("store", actionStore) } const replayAll = (kernelID) => { @@ -68,8 +65,13 @@ const replayAll = (kernelID) => { "from netpyne_ui.netpyne_geppetto import netpyne_geppetto", "netpyne_geppetto.deleteModel({})", ...store.getState()[kernelID]]; - commands.pop() // we drop the last command which is probably the faulty one - execPythonMessageWithoutRecording(commands.join('\n')) + const lastCommand = commands.pop() // we drop the last command which is probably the faulty one + const script = commands.join('\n') + console.log("Playing", script) + console.log("Skipping last command", lastCommand) + execPythonMessageWithoutRecording(script).then(() => { + store.dispatch(dropLastCommand(kernelID)) + }) } export { record, replayAll } \ No newline at end of file diff --git a/webapp/components/general/GeppettoJupyterUtils.js b/webapp/components/general/GeppettoJupyterUtils.js index 0b61e3fc..013d884d 100644 --- a/webapp/components/general/GeppettoJupyterUtils.js +++ b/webapp/components/general/GeppettoJupyterUtils.js @@ -34,7 +34,6 @@ const handle_output = function (data) { const execPythonMessage = function (command, callback = handle_output, record = true) { const { kernel } = IPython.notebook; - console.log("Kernel", kernel.id, "executes", command) if (record) { recordCommand(kernel.id, command) } diff --git a/webapp/redux/actiondomainStore.js b/webapp/redux/actiondomainStore.js index 0d3d400e..7d010681 100644 --- a/webapp/redux/actiondomainStore.js +++ b/webapp/redux/actiondomainStore.js @@ -1,5 +1,6 @@ // import action types -import { RECORD_COMMAND, REPLAY_COMMANDS } from "./actions/actiondomain"; +import { DROP_LAST_COMMAND, FLUSH_COMMANDS, RECORD_COMMAND } from "./actions/actiondomain"; +// import redux from 'redux' // Default state for general export const ACTION_DOMAIN_DEFAULT_STATE = { @@ -15,8 +16,18 @@ const recordCommand = (state, { kernel, command }) => { return newState; } -const replayCommands = (state, { kernel }) => { - console.log("TODO, REPLAY COMMANDS") +const dropLastCommand = (state, { kernel }) => { + const newState = { ...state }; + if (!newState[kernel]) { + newState[kernel] = [] + return newState + } + newState[kernel].pop() + return newState; +} + +const flushCommands = (state, { kernel }) => { + return { ...state, [kernel]: [] }; } // reducer @@ -24,8 +35,10 @@ const reducer = (state = ACTION_DOMAIN_DEFAULT_STATE, action) => { switch (action.type) { case RECORD_COMMAND: return recordCommand(state, action.payload) - case REPLAY_COMMANDS: - return replayCommands(state, action.payload) + case DROP_LAST_COMMAND: + return dropLastCommand(state, action.payload) + case FLUSH_COMMANDS: + return flushCommands(state, action.payload) default: return state } diff --git a/webapp/redux/actions/actiondomain.js b/webapp/redux/actions/actiondomain.js index 0afeb40d..31d91b25 100644 --- a/webapp/redux/actions/actiondomain.js +++ b/webapp/redux/actions/actiondomain.js @@ -1,6 +1,7 @@ // Action Types export const RECORD_COMMAND = 'RECORD_COMMAND'; -export const REPLAY_COMMANDS = 'REPLAY_COMMANDS'; +export const DROP_LAST_COMMAND = 'DROP_LAST_COMMAND'; +export const FLUSH_COMMANDS = 'FLUSH_COMMANDS'; // Actions export const recordCommand = (kernelID, command) => ({ @@ -11,8 +12,15 @@ export const recordCommand = (kernelID, command) => ({ } }); -export const replayCommands = (kernelID) => ({ - type: REPLAY_COMMANDS, +export const dropLastCommand = (kernelID) => ({ + type: DROP_LAST_COMMAND, + payload: { + kernel: kernelID + } +}) + +export const flushCommands = (kernelID) => ({ + type: FLUSH_COMMANDS, payload: { kernel: kernelID } From 9d83db9bfd52d8551ec90b4cf61ad22a4295e5e4 Mon Sep 17 00:00:00 2001 From: aranega Date: Wed, 8 Nov 2023 07:53:34 -0600 Subject: [PATCH 05/14] netpyne-44 Update kernel listener internal state machine --- webapp/components/NetPyNE.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/webapp/components/NetPyNE.js b/webapp/components/NetPyNE.js index 2269af15..44bb6b25 100644 --- a/webapp/components/NetPyNE.js +++ b/webapp/components/NetPyNE.js @@ -93,10 +93,10 @@ class NetPyNE extends React.Component { // Logic for kernel reinit const logme = ({ detail: { kernel, type } }) => { - console.log("!!!! EVENT", type) switch (this.kernelRestartState.state) { case "restarting": if (type === "kernel_ready" || type === "kernel_autorestarting") { + console.log("Replaying all commands since the beginning of the session") replayAll(this.kernelRestartState.kernelID) this.kernelRestartState = { state: "idle", @@ -104,12 +104,28 @@ class NetPyNE extends React.Component { } } case "idle": + if (type === "kernel_connected") { + console.log("Kernel is connecting/starting, being init") + this.kernelRestartState = { + state: "init", + kernelID: kernel.id + } + } if (type === "kernel_restarting") { + console.log("Kernel restart event caught, trying to re-init the current model") this.kernelRestartState = { state: "restarting", kernelID: kernel.id } } + case "init": + if (type === "kernel_ready") { + console.log("Kernel properly initialized") + this.kernelRestartState = { + state: "idle", + kernelID: undefined + } + } } } window.addEventListener('kernelstatus', logme) @@ -143,6 +159,9 @@ class NetPyNE extends React.Component {
+ {/* */}
From 391a9b611d9aaf073f3b97e71b331624cdffbb8a Mon Sep 17 00:00:00 2001 From: aranega Date: Wed, 8 Nov 2023 09:25:16 -0600 Subject: [PATCH 06/14] netpyne-44 Fix issue with non reopening modal --- webapp/components/topbar/dialogs/ActionDialog.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webapp/components/topbar/dialogs/ActionDialog.js b/webapp/components/topbar/dialogs/ActionDialog.js index f3d12264..4c7dd144 100644 --- a/webapp/components/topbar/dialogs/ActionDialog.js +++ b/webapp/components/topbar/dialogs/ActionDialog.js @@ -46,10 +46,13 @@ class ActionDialog extends React.Component { } } } - this.setState({ hide: true }); if (this.props.onAction) { this.props.onAction(); } + this.setState({ hide: true }); + if (this.props.onRequestClose) { + this.props.onRequestClose(); + } }; clearErrorDialogBox () { From 8fa70f98aaa339319d721c1989a07b6598d4e445 Mon Sep 17 00:00:00 2001 From: aranega Date: Thu, 9 Nov 2023 00:50:42 -0600 Subject: [PATCH 07/14] netpyne-44 Fix Dialog component --- webapp/components/general/Dialog.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/webapp/components/general/Dialog.js b/webapp/components/general/Dialog.js index 954269b2..ab4f9698 100644 --- a/webapp/components/general/Dialog.js +++ b/webapp/components/general/Dialog.js @@ -1,14 +1,7 @@ import React from 'react'; -import Button from '@material-ui/core/Button'; import MuiDialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import Typography from '@material-ui/core/Typography'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Paper from '@material-ui/core/Paper'; -import Box from '@material-ui/core/Box'; -import Link from '@material-ui/core/Link'; -import Icon from '@material-ui/core/Icon'; +import { Button,DialogActions, DialogContent, DialogTitle, DialogContentText } from '@material-ui/core'; +import { Typography, Paper, Box, Link, Icon } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import { secondaryColor, bgLight } from '../../theme'; import logoNetpyne from '../../static/netpyne-logo_white.png'; @@ -135,18 +128,31 @@ const ContributeContent = withStyles(styles)(({ classes }) => ( )); + +const titleContentMapping = { + Contribute: , + About: +} + export default function Dialog ({ open, title, message, handleClose, }) { + const selectMessageContent = () => { + if (title in titleContentMapping) { + return titleContentMapping[title] + } + return {message} + } return (
{title} - {title === 'Contribute' ? : } + {/* {title === 'Contribute' ? : } */} + {selectMessageContent()} */} + }}>CRASH ME
From c2d7bb0762c3668d52648be92affa26ec91f8f73 Mon Sep 17 00:00:00 2001 From: aranega Date: Thu, 9 Nov 2023 00:52:49 -0600 Subject: [PATCH 10/14] netpyne-44 Remove crash button --- webapp/components/NetPyNE.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/components/NetPyNE.js b/webapp/components/NetPyNE.js index 6e7dfe33..4b268e73 100644 --- a/webapp/components/NetPyNE.js +++ b/webapp/components/NetPyNE.js @@ -13,7 +13,7 @@ import { TutorialObserver, } from 'netpyne/components'; import { loadModel, openDialog } from '../redux/actions/general'; -import { execPythonMessage } from './general/GeppettoJupyterUtils'; +// import { execPythonMessage } from './general/GeppettoJupyterUtils'; import { replayAll } from './general/CommandRecorder'; const styles = ({ zIndex }) => ({ @@ -163,9 +163,9 @@ class NetPyNE extends React.Component {
- + }}>CRASH ME */}
From f2bec70b1d7dac1358784283c646f127323e3dee Mon Sep 17 00:00:00 2001 From: aranega Date: Thu, 9 Nov 2023 01:24:53 -0600 Subject: [PATCH 11/14] netpyne-44 Differenciate restart from unexpected restart --- webapp/components/NetPyNE.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/components/NetPyNE.js b/webapp/components/NetPyNE.js index 4b268e73..19d09c55 100644 --- a/webapp/components/NetPyNE.js +++ b/webapp/components/NetPyNE.js @@ -111,7 +111,7 @@ class NetPyNE extends React.Component { kernelID: kernel.id } } - if (type === "kernel_restarting") { + if (type === "kernel_autorestarting") { console.log("Kernel restart event caught, trying to re-init the current model") this.kernelRestartState = { state: "restarting", From f945d4af439a6925cc79a404436992e4d5c94049 Mon Sep 17 00:00:00 2001 From: aranega Date: Thu, 16 Nov 2023 09:25:15 -0600 Subject: [PATCH 12/14] netpyne-44 Add auto-save from the initial point --- webapp/components/NetPyNE.js | 23 +++++++++++++++++++- webapp/components/general/CommandRecorder.js | 1 + webapp/redux/middleware/middleware.js | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/webapp/components/NetPyNE.js b/webapp/components/NetPyNE.js index 19d09c55..7de0c791 100644 --- a/webapp/components/NetPyNE.js +++ b/webapp/components/NetPyNE.js @@ -111,7 +111,7 @@ class NetPyNE extends React.Component { kernelID: kernel.id } } - if (type === "kernel_autorestarting") { + else if (type === "kernel_autorestarting") { console.log("Kernel restart event caught, trying to re-init the current model") this.kernelRestartState = { state: "restarting", @@ -122,6 +122,13 @@ class NetPyNE extends React.Component { message: "An action occured that made the kernel restart. We are reloading your model and all the actions you applied on it." })) } + else if (type === "kernel_restarting") { + console.log("Kernel restart, perhaps it's a special restart?") + this.kernelRestartState = { + state: "special_restart", + kernelID: kernel.id + } + } case "init": if (type === "kernel_ready") { console.log("Kernel properly initialized") @@ -130,6 +137,20 @@ class NetPyNE extends React.Component { kernelID: undefined } } + case "special_restart": + if (type == "kernel_autorestarting") { + console.log("Kernel autorestart after a start, we might not have the ready event, we force it then") + replayAll(this.kernelRestartState.kernelID) + this.kernelRestartState = { + state: 'restarting', + kernelID: kernel.id + } + } else { + this.kernelRestartState = { + state: 'idle', + kernelID: undefined + } + } } } window.addEventListener('kernelstatus', handleKernelRestart) diff --git a/webapp/components/general/CommandRecorder.js b/webapp/components/general/CommandRecorder.js index 5bbeef6b..65b5cbfc 100644 --- a/webapp/components/general/CommandRecorder.js +++ b/webapp/components/general/CommandRecorder.js @@ -64,6 +64,7 @@ const replayAll = (kernelID) => { "from jupyter_geppetto import utils", "from netpyne_ui.netpyne_geppetto import netpyne_geppetto", "netpyne_geppetto.deleteModel({})", + "netpyne_geppetto.loadFromIndexFile('/tmp/tmpmodel')", ...store.getState()[kernelID]]; const lastCommand = commands.pop() // we drop the last command which is probably the faulty one const script = commands.join('\n') diff --git a/webapp/redux/middleware/middleware.js b/webapp/redux/middleware/middleware.js index 7e3f3dfa..2c5ffdbb 100644 --- a/webapp/redux/middleware/middleware.js +++ b/webapp/redux/middleware/middleware.js @@ -290,6 +290,7 @@ export default (store) => (next) => (action) => { next(GeppettoActions.modelLoaded()) getExperiments() }); + Utils.execPythonMessage('netpyne_geppetto.saveToIndexFile(None, "/tmp/tmpmodel", True, True)', ()=>{}, false); setTimeout(() => { if (!responded) { From 87d0140f2fbb1964912683086ff89f1001e71e48 Mon Sep 17 00:00:00 2001 From: aranega Date: Mon, 20 Nov 2023 04:14:59 -0600 Subject: [PATCH 13/14] netpyne-44 Add handling of kernel crash loop --- webapp/components/NetPyNE.js | 56 ++++++++++++++++---- webapp/components/general/CommandRecorder.js | 37 ++++++++++--- webapp/constants.js | 4 ++ webapp/redux/actiondomainStore.js | 13 ++++- webapp/redux/actions/actiondomain.js | 9 ++++ webapp/redux/middleware/middleware.js | 2 +- 6 files changed, 104 insertions(+), 17 deletions(-) diff --git a/webapp/components/NetPyNE.js b/webapp/components/NetPyNE.js index 7de0c791..68fbd0b5 100644 --- a/webapp/components/NetPyNE.js +++ b/webapp/components/NetPyNE.js @@ -50,7 +50,8 @@ class NetPyNE extends React.Component { this.loaded = false; this.kernelRestartState = { state: "idle", - kernelID: undefined + kernelID: undefined, + crashLoop: false } } @@ -99,14 +100,16 @@ class NetPyNE extends React.Component { console.log("Replaying all commands since the beginning of the session") replayAll(this.kernelRestartState.kernelID) this.kernelRestartState = { + ...this.kernelRestartState, state: "idle", - kernelID: undefined + kernelID: undefined, } } case "idle": if (type === "kernel_connected") { console.log("Kernel is connecting/starting, being init") this.kernelRestartState = { + ...this.kernelRestartState, state: "init", kernelID: kernel.id } @@ -114,17 +117,21 @@ class NetPyNE extends React.Component { else if (type === "kernel_autorestarting") { console.log("Kernel restart event caught, trying to re-init the current model") this.kernelRestartState = { + ...this.kernelRestartState, state: "restarting", kernelID: kernel.id } - this.props.dispatchAction(openDialog({ - title: "Kernel restart", - message: "An action occured that made the kernel restart. We are reloading your model and all the actions you applied on it." - })) + if (!this.kernelRestartState.crashLoop) { + this.props.dispatchAction(openDialog({ + title: "Kernel restart", + message: "An action occured that made the kernel restart. We are reloading your model and all the actions you applied on it." + })) + } } else if (type === "kernel_restarting") { console.log("Kernel restart, perhaps it's a special restart?") this.kernelRestartState = { + ...this.kernelRestartState, state: "special_restart", kernelID: kernel.id } @@ -133,8 +140,9 @@ class NetPyNE extends React.Component { if (type === "kernel_ready") { console.log("Kernel properly initialized") this.kernelRestartState = { + ...this.kernelRestartState, state: "idle", - kernelID: undefined + kernelID: undefined, } } case "special_restart": @@ -142,18 +150,48 @@ class NetPyNE extends React.Component { console.log("Kernel autorestart after a start, we might not have the ready event, we force it then") replayAll(this.kernelRestartState.kernelID) this.kernelRestartState = { + ...this.kernelRestartState, state: 'restarting', - kernelID: kernel.id + kernelID: kernel.id, } } else { + console.log("Regular restart detected") this.kernelRestartState = { + ...this.kernelRestartState, state: 'idle', - kernelID: undefined + kernelID: undefined, } } } } window.addEventListener('kernelstatus', handleKernelRestart) + + // Dedicated code to handle crash loops + const kernelRestartLoopHandler = ( ) => { + if (!this.kernelRestartState.crashLoop) { + this.props.dispatchAction(openDialog({ + title: "Kernel restart loop stabilization", + message: "One of your actions triggered a kernel restart loop. We are trying to identify the faulty command and to restore your model until this point. Close this window and wait for the kernel stabilization notification." + })) + } else { + clearTimeout(this.kernelRestartState.crashLoop) + } + const taskID = setTimeout((_this) => { + this.props.dispatchAction(openDialog({ + title: "Kernel restart loop stabilized", + message: "The kernel is now stabilized." + })); + this.kernelRestartState = { + ...this.kernelRestartState, + crashLoop: false + } + }, 8000, this) + this.kernelRestartState = { + ...this.kernelRestartState, + crashLoop: taskID + } + } + window.addEventListener('kernelRestartLoop', kernelRestartLoopHandler) } componentWillUnmount () { diff --git a/webapp/components/general/CommandRecorder.js b/webapp/components/general/CommandRecorder.js index 65b5cbfc..81bc975a 100644 --- a/webapp/components/general/CommandRecorder.js +++ b/webapp/components/general/CommandRecorder.js @@ -1,6 +1,7 @@ +import { KERNEL_HANDLING } from '../../constants'; import { store } from '../../redux/actiondomainStore' -import { recordCommand, dropLastCommand } from '../../redux/actions/actiondomain'; -import { execPythonMessageWithoutRecording } from './GeppettoJupyterUtils'; +import { recordCommand, dropLastCommand, dropFromIndex } from '../../redux/actions/actiondomain'; +import { execPythonMessage, execPythonMessageWithoutRecording } from './GeppettoJupyterUtils'; const registerKernelListeners = () => { @@ -58,18 +59,42 @@ const record = (kernelID, command) => { store.dispatch(recordCommand(kernelID, command)) } -const replayAll = (kernelID) => { - const commands = [ +const TIMEFRAME = 10 * 1000; // 10s +let lastReplayTime = 0 + +const getCommands = (kernelID) => { + return [ "from jupyter_geppetto import jupyter_geppetto", "from jupyter_geppetto import utils", "from netpyne_ui.netpyne_geppetto import netpyne_geppetto", "netpyne_geppetto.deleteModel({})", - "netpyne_geppetto.loadFromIndexFile('/tmp/tmpmodel')", - ...store.getState()[kernelID]]; + `netpyne_geppetto.loadFromIndexFile("${KERNEL_HANDLING.tmpModelPath}")`, + ...store.getState()[kernelID] + ] +} + +const replayAll = (kernelID, fromRec = false) => { + const currentTimestamp = Date.now(); + const commands = getCommands(kernelID); + + if (!fromRec && currentTimestamp - lastReplayTime < TIMEFRAME) { + const restartLoop = new CustomEvent("kernelRestartLoop", { + detail: { + "kernel": kernelID, + "state": "looping" + } + }); + window.dispatchEvent(restartLoop); + store.dispatch(dropLastCommand(kernelID)) + replayAll(kernelID, true) + return + } + const lastCommand = commands.pop() // we drop the last command which is probably the faulty one const script = commands.join('\n') console.log("Playing", script) console.log("Skipping last command", lastCommand) + lastReplayTime = currentTimestamp execPythonMessageWithoutRecording(script).then(() => { store.dispatch(dropLastCommand(kernelID)) }) diff --git a/webapp/constants.js b/webapp/constants.js index c7cc0722..3d64738e 100644 --- a/webapp/constants.js +++ b/webapp/constants.js @@ -746,3 +746,7 @@ export const geometryStrings = [ 'inside', 'membrane' ] + +export const KERNEL_HANDLING = { + tmpModelPath: '/tmp/tmpmodel' +} \ No newline at end of file diff --git a/webapp/redux/actiondomainStore.js b/webapp/redux/actiondomainStore.js index 7d010681..67654b0b 100644 --- a/webapp/redux/actiondomainStore.js +++ b/webapp/redux/actiondomainStore.js @@ -1,5 +1,5 @@ // import action types -import { DROP_LAST_COMMAND, FLUSH_COMMANDS, RECORD_COMMAND } from "./actions/actiondomain"; +import { DROP_FROM_INDEX, DROP_LAST_COMMAND, FLUSH_COMMANDS, RECORD_COMMAND } from "./actions/actiondomain"; // import redux from 'redux' // Default state for general @@ -30,6 +30,15 @@ const flushCommands = (state, { kernel }) => { return { ...state, [kernel]: [] }; } +const dropFromIndex = (state, { kernel, index }) => { + const newState = { ...state }; + if (!newState[kernel]) { + newState[kernel] = [] + } + newState[kernel] = newState[kernel].splice(0, index) + return newState; +} + // reducer const reducer = (state = ACTION_DOMAIN_DEFAULT_STATE, action) => { switch (action.type) { @@ -39,6 +48,8 @@ const reducer = (state = ACTION_DOMAIN_DEFAULT_STATE, action) => { return dropLastCommand(state, action.payload) case FLUSH_COMMANDS: return flushCommands(state, action.payload) + case DROP_FROM_INDEX: + return dropFromIndex(state, action.payload) default: return state } diff --git a/webapp/redux/actions/actiondomain.js b/webapp/redux/actions/actiondomain.js index 31d91b25..65812445 100644 --- a/webapp/redux/actions/actiondomain.js +++ b/webapp/redux/actions/actiondomain.js @@ -1,6 +1,7 @@ // Action Types export const RECORD_COMMAND = 'RECORD_COMMAND'; export const DROP_LAST_COMMAND = 'DROP_LAST_COMMAND'; +export const DROP_FROM_INDEX = 'DROP_FROM_INDEX'; export const FLUSH_COMMANDS = 'FLUSH_COMMANDS'; // Actions @@ -19,6 +20,14 @@ export const dropLastCommand = (kernelID) => ({ } }) +export const dropFromIndex = (kernelID, index) => ({ + type: DROP_FROM_INDEX, + payload: { + kernel: kernelID, + index: index + } +}) + export const flushCommands = (kernelID) => ({ type: FLUSH_COMMANDS, payload: { diff --git a/webapp/redux/middleware/middleware.js b/webapp/redux/middleware/middleware.js index 2c5ffdbb..432b3540 100644 --- a/webapp/redux/middleware/middleware.js +++ b/webapp/redux/middleware/middleware.js @@ -290,7 +290,7 @@ export default (store) => (next) => (action) => { next(GeppettoActions.modelLoaded()) getExperiments() }); - Utils.execPythonMessage('netpyne_geppetto.saveToIndexFile(None, "/tmp/tmpmodel", True, True)', ()=>{}, false); + Utils.execPythonMessage(`netpyne_geppetto.saveToIndexFile(None, "${Constants.KERNEL_HANDLING.tmpModelPath}", True, True)`, ()=>{}, false); setTimeout(() => { if (!responded) { From 3ef31857f2a92dea2570845ce6103bf0cca515e3 Mon Sep 17 00:00:00 2001 From: aranega Date: Mon, 27 Nov 2023 05:19:54 -0600 Subject: [PATCH 14/14] netpyne-44 Do not record simulation commands --- webapp/components/general/GeppettoJupyterUtils.js | 4 ++-- webapp/redux/middleware/middleware.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/components/general/GeppettoJupyterUtils.js b/webapp/components/general/GeppettoJupyterUtils.js index 013d884d..276df8d0 100644 --- a/webapp/components/general/GeppettoJupyterUtils.js +++ b/webapp/components/general/GeppettoJupyterUtils.js @@ -65,7 +65,7 @@ const addslashes = function (str) { return (str + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0'); } -const evalPythonMessage = function (command, parameters, parse = true) { +const evalPythonMessage = function (command, parameters, parse = true, record = true) { let parametersString = ''; if (parameters) { if (parameters.length > 0) { @@ -79,7 +79,7 @@ const evalPythonMessage = function (command, parameters, parse = true) { if (parse) { finalCommand = `utils.convertToJS(${finalCommand})`; } - return execPythonMessage(finalCommand, handle_output); + return execPythonMessage(finalCommand, handle_output, record); }; export { execPythonMessage, evalPythonMessage, execPythonMessageWithoutRecording }; diff --git a/webapp/redux/middleware/middleware.js b/webapp/redux/middleware/middleware.js index 432b3540..daf1fedf 100644 --- a/webapp/redux/middleware/middleware.js +++ b/webapp/redux/middleware/middleware.js @@ -97,7 +97,7 @@ function isGeppettoModel(obj) { const createSimulateBackendCall = async (cmd, payload, consoleMessage, spinnerType) => { console.log(consoleMessage); - const response = await Utils.evalPythonMessage(cmd, [payload]); + const response = await Utils.evalPythonMessage(cmd, [payload], true, false); console.log('Python response', response); const responsePayload = processError(response);