more-compute 0.3.2__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: more-compute
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: An interactive notebook environment for local and GPU computing
5
5
  Home-page: https://github.com/DannyMang/MORECOMPUTE
6
6
  Author: MoreCompute Team
@@ -44,7 +44,11 @@ Dynamic: requires-python
44
44
  [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
45
45
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
46
46
 
47
- An interactive notebook environment, similar to Marimo and Google Colab, that runs locally. It works with standard `.ipynb` files, similar to Jupyter Lab but more awesome.
47
+ An interactive Python notebook environment, similar to Marimo and Google Colab, that runs locally.
48
+
49
+
50
+ https://github.com/user-attachments/assets/8c7ec716-dade-4de2-ad37-71d328129c97
51
+
48
52
 
49
53
  ## Installation
50
54
 
@@ -75,40 +79,46 @@ pip install more-compute
75
79
  ## Usage
76
80
 
77
81
  ```bash
78
- more-compute notebook.ipynb # Open existing notebook
79
- more-compute # Create and open new notebook
82
+ more-compute notebook.py # Open existing notebook
83
+ more-compute new # Create new notebook
80
84
  more-compute --debug # Show logs
81
85
  ```
82
86
 
83
- Opens automatically at http://localhost:8000
87
+ Opens automatically at http://localhost:3141
84
88
 
85
- ## Troubleshooting
89
+ ### Converting Between Formats
86
90
 
87
- **Command not found:**
88
- ```bash
89
- uv tool update-shell # Fixes PATH automatically
90
- ```
91
+ MoreCompute uses `.py` notebooks with `# %%` cell markers, but you can convert to/from `.ipynb`:
91
92
 
92
- **Manual PATH fix (macOS/Linux):**
93
+ **From .ipynb to .py:**
93
94
  ```bash
94
- echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
95
- source ~/.bashrc
96
- ```
95
+ # Auto-detect output name (notebook.ipynb -> notebook.py)
96
+ more-compute convert notebook.ipynb
97
97
 
98
- **Manual PATH fix (Windows):**
99
- ```powershell
100
- $pythonScripts = python -c "import site; print(site.USER_BASE)"
101
- $userPath = [Environment]::GetEnvironmentVariable("Path", "User")
102
- [Environment]::SetEnvironmentVariable("Path", "$userPath;$pythonScripts\Scripts", "User")
103
- # Restart PowerShell
98
+ # Or specify output
99
+ more-compute convert notebook.ipynb -o my_notebook.py
100
+
101
+ # Then open in MoreCompute
102
+ more-compute my_notebook.py
104
103
  ```
105
104
 
106
- **Port in use:**
105
+ The converter automatically extracts dependencies from `!pip install` commands and adds UV inline script metadata.
106
+
107
+ **From .py to .ipynb:**
107
108
  ```bash
108
- export MORECOMPUTE_PORT=8080 # macOS/Linux
109
- $env:MORECOMPUTE_PORT = "8080" # Windows
109
+ # Auto-detect output name (notebook.py -> notebook.ipynb)
110
+ more-compute convert notebook.py
111
+
112
+ # Or specify output
113
+ more-compute convert notebook.py -o colab_notebook.ipynb
110
114
  ```
111
115
 
116
+ This makes your notebooks compatible with Google Colab, Jupyter, and other tools that require `.ipynb` format.
117
+
118
+ ## Troubleshooting
119
+
120
+ will add things here as things progress...
121
+
112
122
  ## Development
113
123
 
114
124
  ```bash
@@ -117,7 +127,7 @@ cd MORECOMPUTE
117
127
  uv venv && source .venv/bin/activate
118
128
  uv pip install -e .
119
129
  cd frontend && npm install && cd ..
120
- python kernel_run.py notebook.ipynb
130
+ more-compute notebook.py
121
131
  ```
122
132
 
123
133
  ## License
@@ -1,11 +1,10 @@
1
- kernel_run.py,sha256=sQchzBOWgynXOKkSMp8T8BAkljw5CqDFrHX2Y0U6yto,16526
2
- frontend/.DS_Store,sha256=uQeHnkKyuTF1AVax3NPqtN0uCH6XNXAxL9Nkb6Q9cGw,8196
1
+ kernel_run.py,sha256=tY8n3U4-mB3PYwVWDEOe5x0v67iXMOWpIrWNFqcm7JE,21540
3
2
  frontend/.gitignore,sha256=IH4mX_SQH5rZ-W2M4IUw4E-fxgCBVHKmbQpEYJbWVM0,480
4
3
  frontend/README.md,sha256=YLVf9995r3JZD5UkII5GZCvDK9wXXNrUE0loHA4vlY8,1450
5
4
  frontend/__init__.py,sha256=L5SAOdfDfKqlgEVCvYQQRDZBTlCxutZKSpJp4018IG4,100
6
5
  frontend/eslint.config.mjs,sha256=LBCCw4SomtiVMmlTSpYRXfkRs6Xs04R1YFfoyorYyT8,879
7
6
  frontend/next-env.d.ts,sha256=ha5a7nXwEZZ88tJcvDQvYtaTFOnZJff0qjRW_Cz_zKY,262
8
- frontend/next.config.mjs,sha256=n0o6cIIVIoOtI6JlvAK-HUFd2lg1pQPfUwlFS4O6TK0,346
7
+ frontend/next.config.mjs,sha256=hxSST3ls40dm0NL0F8J9yPkHXFQZNQ_JenO7qDxbYSQ,346
9
8
  frontend/next.config.ts,sha256=OL_rEfTIZxsB_B5R9JX2AxYXgC0Fc_XiDoRlOGEDEpk,368
10
9
  frontend/package-lock.json,sha256=pGL_J72WHNTz7iLx2MOwHwnDGfbKzN-r_qnnbzfGbEU,334369
11
10
  frontend/package.json,sha256=nUVMqlXGT5m431ArOzzs_geSfmq0AKfqUB40zIAeZ5s,1395
@@ -14,20 +13,20 @@ frontend/styling_README.md,sha256=jvoXKUzJv0rEIU40gz23Z6Xugy4To8P0N9szg_hKrqw,56
14
13
  frontend/tailwind.config.ts,sha256=eP9nVaAuyYo46vGQfCyWbo25_pr2hW830fs1Itcix9Q,620
15
14
  frontend/tsconfig.json,sha256=7SvBlRBYmuXAlAteRQTGwEE7ooWuNaPUrZ219dOo61E,598
16
15
  frontend/app/favicon.ico,sha256=K4rS0zRVqPc2_DqOv48L3qiEitTA20iigzvQ-c13WTI,25931
17
- frontend/app/globals.css,sha256=0k9WgoXRHIQjsI5kVSSkKLfYJs04FYOhOY8JvQrSulg,33823
18
- frontend/app/layout.tsx,sha256=VMkhvePu0hV0w-huEmTocTr7lZ6DF_7eidrhzCCMwzc,7604
16
+ frontend/app/globals.css,sha256=uNJlOuKwesEGBHdZauR2-peTvBR9BH_-5J05enUTdUQ,34048
17
+ frontend/app/layout.tsx,sha256=6xut2wC-CN1aPufPfjJqa2TlYKr8gD6F9KqUlINd0hg,9295
19
18
  frontend/app/page.tsx,sha256=p-DgDv8xDnwcRfDJY4rtfSQ2VdYwbnP3G-otWqDR1l0,256
20
- frontend/components/Notebook.tsx,sha256=JImlThoX4F363yShOqdYDjIIO3i1uqfCLRkDlzurinQ,21851
19
+ frontend/components/Notebook.tsx,sha256=izTlXRI7-JR51f_uSwqV8kcMeuhiVPq_wR30h_8jhhI,22517
21
20
  frontend/components/cell/AddCellButton.tsx,sha256=ZC2Vck0JIRDxGYhYv3LPYAdKDo13U6008WG_-XoPlIM,1012
22
- frontend/components/cell/CellButton.tsx,sha256=j3lRStluVtRXjZOhi9isS3XKsRmM0WTn5SaFDM6GuYg,1386
23
- frontend/components/cell/MonacoCell.tsx,sha256=EqsajBZuQpoOWlNVG7OmmoIyY4pUfW5obiBwpniuQbw,23862
21
+ frontend/components/cell/CellButton.tsx,sha256=7OaedFBBOygLD-AcaF_FyCFBSc9841jhTwC_mDe1JrY,1439
22
+ frontend/components/cell/MonacoCell.tsx,sha256=x0cwutkb-mueaXf-FdXnK6hmUuq1nROD04uq7_aZ5xc,24743
24
23
  frontend/components/layout/ConnectionBanner.tsx,sha256=-m77wKCFpeRJ_AQwnM38jLwCY5vfpqE846xeXmT3p8A,870
25
24
  frontend/components/layout/Sidebar.tsx,sha256=kxgO2mXX11xI6rX478cUqqQ3xB40jlby21y27GTJSLU,1551
26
25
  frontend/components/modals/ConfirmModal.tsx,sha256=3WgXy_wmR8FHZPwzMevfZHFXa0blR4v_SbKG3d5McT4,3652
27
26
  frontend/components/modals/ErrorModal.tsx,sha256=kkGHQvgyMYlScKwni04-V_Dq6a0-lg0WodglLARR-uA,3536
28
27
  frontend/components/modals/SuccessModal.tsx,sha256=7NVg0MFPVvsBGDOHPVyZTNx4kmZLHgxdhZKKtTD9FTU,3385
29
28
  frontend/components/output/CellOutput.tsx,sha256=KLRzwEchvBoeoai8TbabKEwO4tHmog3EKeTJAXtmveI,3665
30
- frontend/components/output/ErrorDisplay.tsx,sha256=kYfip5zikcFIuN7WW47RVjoqwDujC1q_yMbgHrHl0E0,4996
29
+ frontend/components/output/ErrorDisplay.tsx,sha256=va_jMO9mZnrjOWxVZ7nwL1_7-Ii9I5JVwEwAZR-jsNU,5535
31
30
  frontend/components/output/MarkdownRenderer.tsx,sha256=RtZ5yNRxDXIh_NkNsNiy30wMGIW7V1gfhsjecgMdc80,3341
32
31
  frontend/components/popups/ComputePopup.tsx,sha256=B7AwwjpJ9_nNI5qSN53jdR2tAulB5yl7spjMXjH4yDM,44021
33
32
  frontend/components/popups/FilterPopup.tsx,sha256=KRIzRvVxEF47ELWIAvrqv7BZQYmEo_zOX8Nbpz0zEik,14133
@@ -35,13 +34,13 @@ frontend/components/popups/FolderPopup.tsx,sha256=V2tDAbztvNIUyPWtFiwjeIoCmFGQyD
35
34
  frontend/components/popups/MetricsPopup.tsx,sha256=ublYbOQ-pSU_w48F10uFjDYctysEwmri9lrji5YHIC4,6034
36
35
  frontend/components/popups/PackagesPopup.tsx,sha256=2DnwNVN8kwC3ff1UN_7cl6qLwAFE9F9H8hYwl7M3jTM,4141
37
36
  frontend/components/popups/SettingsPopup.tsx,sha256=rGwYxjkADnyTtpP0MjVpFPBsGoT1eENN2z7nWjLuzFE,2773
38
- frontend/contexts/PodWebSocketContext.tsx,sha256=QyEg1Msyum7h3T6F3j6pn4Ds5sXFT_9WYu5_0YZ2IUI,8256
37
+ frontend/contexts/PodWebSocketContext.tsx,sha256=YIC7tDHM4iGYhH_B75Eiye4uveVAzWoXzLqw83obGIs,8256
39
38
  frontend/lib/api.ts,sha256=L6Js3K7RcPCLfVVUz-bKX5hLAVFKCfUSHAKSHk2keAk,11026
40
39
  frontend/lib/monaco-themes.ts,sha256=jh_pZAmSMKjY_belbMbZX2WpFBN7baRxvJp9shUDYgk,5396
41
40
  frontend/lib/settings.ts,sha256=klHVyB2n-wTc8YrjKCIf2BEmrR-JLVHrRYIXkDxv9-A,6263
42
41
  frontend/lib/themes.json,sha256=mk6IGy6o_DCOerBH3QmfXozTHEiy-alsTLTTIaba7No,292018
43
42
  frontend/lib/websocket-native.ts,sha256=KcokVZAl08iWRAcBOfyt3uej352esOa_-YwLk7M5iSU,6046
44
- frontend/lib/websocket.ts,sha256=V2Y7sktt2dm1G-ZILggcqIWccsh-xoAG9dLHpNVqIqs,4500
43
+ frontend/lib/websocket.ts,sha256=K9Pf71fbRTuwbJTToLq9r4vlbv_c3JOMxJlpDinDKMI,4500
45
44
  frontend/public/file.svg,sha256=K2eBLDJcGZoCU2zb7qDFk6cvcH0yO3LuPgjbqwZ1O9Q,391
46
45
  frontend/public/globe.svg,sha256=thS5vxg5JZV2YayFFJj-HYAp_UOmL7_thvniYkpX588,1035
47
46
  frontend/public/next.svg,sha256=VZld-tbstJRaHoVt3KA8XhaqW_E_0htN9qdK55NXvPw,1375
@@ -64,39 +63,41 @@ frontend/public/fonts/Fira.ttf,sha256=dbSM4W7Drd9n_EkfXq8P31KuxbjZ1wtuFiZ8aFebvT
64
63
  frontend/public/fonts/Tiempos.woff2,sha256=h83bJKvAK301wXCMIvK7ZG5j0H2K3tzAfgo0yk4z8OE,13604
65
64
  frontend/public/fonts/VeraMono.ttf,sha256=2kKB3H2xej385kpiztkodcWJU0AFXsi6JKORTrl7NJ0,49224
66
65
  frontend/types/notebook.ts,sha256=v23RaZe6H3lU5tq6sqnJDPxC2mu0NZFDCJfiN0mgvSs,1359
67
- more_compute-0.3.2.dist-info/licenses/LICENSE,sha256=0Ot-XIetYt06iay6IhtpJkruD-cLZtjyv7_aIEE-oSc,1073
66
+ more_compute-0.4.0.dist-info/licenses/LICENSE,sha256=0Ot-XIetYt06iay6IhtpJkruD-cLZtjyv7_aIEE-oSc,1073
68
67
  morecompute/__init__.py,sha256=pcMVq8Q7qb42AOn7tqgoZJOi3epDDBnEriiv2WVKnXY,87
69
- morecompute/__version__.py,sha256=vNiWJ14r_cw5t_7UDqDQIVZvladKFGyHH2avsLpN7Vg,22
68
+ morecompute/__version__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
70
69
  morecompute/cli.py,sha256=kVvzvPBqF8xO6UuhU_-TBn99nKwJ405R2mAS6zU0KBc,734
71
- morecompute/notebook.py,sha256=KEcv0eOEh9N7bPVGoRuKJb47G9MmpQ5zz1B6Dm58w18,4651
70
+ morecompute/notebook.py,sha256=RubIXps925vCFgHznkW0QvvW0I11p6IGDDOrhnZ4jYs,6824
72
71
  morecompute/process_worker.py,sha256=KsE3r-XpkYGuyO4w3t54VKkD51LfNHAZc3TYattMtrg,7185
73
- morecompute/server.py,sha256=mdN80iBUixq4KUQfu53aoVsNZ6DM9RaBXc-lwt4bxFk,40285
72
+ morecompute/server.py,sha256=mcJwIvT9QHfpKuAq5jP2SW5hPjFB_rySevgQvOHuud8,41689
74
73
  morecompute/execution/__init__.py,sha256=jPmBmq8BZWbUEY9XFSpqt5FkgX04uNS10WnUlr7Rnms,134
75
74
  morecompute/execution/__main__.py,sha256=pAWB_1bn99u8Gb-tVMSMI-NYvbYbDiwbn40L0a0djeA,202
76
- morecompute/execution/executor.py,sha256=BesGdx10HSGj8PyPrekdQZzc0tXoqidznEaFITOq8JM,20554
77
- morecompute/execution/worker.py,sha256=-_-KD89LVA1DmGa1fTqKekttTOGX_JMtUdO02-SV5DE,16388
75
+ morecompute/execution/executor.py,sha256=UaHsMLfbj4-jKI61vt2EhHF9-9fe01EZZwiFQPsnUMg,25912
76
+ morecompute/execution/worker.py,sha256=i1FbsIGGGqRllceerYnwR0p-M18U6Ez32aeOLhpBL9s,27529
78
77
  morecompute/models/__init__.py,sha256=VLJ5GWi2uTNiZBdvl-ipSbmA6EL8FZHZ5oq-rJmm9z0,640
79
78
  morecompute/models/api_models.py,sha256=-ydvi9SeTfdoY9oVPNQS4by-kQGSknx6BHhGD8E2tpo,4553
80
79
  morecompute/services/data_manager.py,sha256=c4GKucetMM-VPNbHyzce6bZRvFfmz8kTd5RppLjoLVc,14329
81
80
  morecompute/services/lsp_service.py,sha256=Le8ARImcg2P6oueF_14L8rStHOOseHruRTd_wfDVw7s,12237
82
- morecompute/services/pod_manager.py,sha256=rg6mQJOieElDaO-KGBmg7wO6APkBThLpWmDi472Mvbw,21068
81
+ morecompute/services/pod_manager.py,sha256=FxhhsvxN4N4u1_RSqp6sNyvZy1Aws5_dumC-rQ8EvWI,21072
83
82
  morecompute/services/pod_monitor.py,sha256=Y5aiNoVsvkGiHddNbfR1laAKn8G0eY0_nJyTM4VVkyg,4711
84
83
  morecompute/services/prime_intellect.py,sha256=b705rHv3RPRsgWedRlHwoP_S-TxxZtMSyZhnaiZpMgk,10273
85
84
  morecompute/static/styles.css,sha256=el_NtrUMbAUNSiMVBn1xlG70m3iPv7dyaIbWQMexhsY,19277
86
85
  morecompute/utils/__init__.py,sha256=VIxCL3S1pnjEs4cjKGZqZB68_P8FegdeMIqBjJhI5jQ,419
87
86
  morecompute/utils/cache_util.py,sha256=lVlXudHvtyvSo_kCSxORJrI85Jod8FrQLbI2f_JOIbA,661
88
- morecompute/utils/cell_magics.py,sha256=XUxy6lIsrx-9r0mWn0smI1gvT3-kv0rhN5km9cs3D48,27278
89
- morecompute/utils/config_util.py,sha256=fGTQll7Zh05ZHrW8LuQNTJGziGnIfvKIU3azbrY-I-s,1793
87
+ morecompute/utils/cell_magics.py,sha256=YLxltTBprCA8jfgsjqf7Shnk4NCmGKIp3aV5_CYkKRM,29072
88
+ morecompute/utils/config_util.py,sha256=I90om4Wf4BODc5Gy9u8Tu34AcqpkfkGAKQO6JE_NMzU,1940
90
89
  morecompute/utils/error_utils.py,sha256=e50WLFdD6ngIC30xAgrzdTYtD8tPOIFkKAAh_sPbK0I,11667
91
90
  morecompute/utils/line_magics.py,sha256=kTutYBPAWoURY_pk8HXQ38IP712M2rBBfUg3oN8VrP0,33740
91
+ morecompute/utils/notebook_converter.py,sha256=aI9JdgPO8zpE0vYcBGlMEvQT2rLmZtjP_VD5bUn80bY,4229
92
92
  morecompute/utils/notebook_util.py,sha256=3hH94dtXvhizRVTU9a2b38m_51Y4igoXpkjAXUqpVBQ,1353
93
+ morecompute/utils/py_percent_parser.py,sha256=_CVB7uP8Qu8X_yIGKv-k3Lhpw7saH67VofYvRDfGGw8,5087
93
94
  morecompute/utils/python_environment_util.py,sha256=l8WWWPwKbypknw8GwL22NXCji5i1FOy1vWG47J6og4g,7441
94
95
  morecompute/utils/shell_utils.py,sha256=fGFLhQLZU-lmMGALbbS-fKPkhQtmMhZ1FkgQ3TeoFhA,1917
95
- morecompute/utils/special_commands.py,sha256=IyF9MTINMNNo3P_f56Q6yS_P2HNjA9vohYVxEV7KYnc,17331
96
+ morecompute/utils/special_commands.py,sha256=nf2nVea5SyFqpulNYrnRx7anU4-G-e5_kLJ0PLtEi3Q,22030
96
97
  morecompute/utils/system_environment_util.py,sha256=32mQRubo0i4X61o-825T7m-eUSidcEp07qkInP1sWZA,4774
97
98
  morecompute/utils/zmq_util.py,sha256=tx7-iS04UN69OFtBzkxcEnRhT7xtI9EzRnrZ_nsH_O0,1889
98
- more_compute-0.3.2.dist-info/METADATA,sha256=qO93-kelqgnC3wWl2NzLe_JyMfOnGtBJ3wPY5WqadrI,3631
99
- more_compute-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
100
- more_compute-0.3.2.dist-info/entry_points.txt,sha256=xp7z9eRPNRM4oxkZZVlyXkhkSjN1AjoYI_B7qpDJ1bI,49
101
- more_compute-0.3.2.dist-info/top_level.txt,sha256=Tamm6ADzjwaQa1z27O7Izcyhyt9f0gVjMv1_tC810aI,32
102
- more_compute-0.3.2.dist-info/RECORD,,
99
+ more_compute-0.4.0.dist-info/METADATA,sha256=RBEFHSXqQsBN5zbiANyf86r1cHoEEPFpzwNbIVKRTLA,3877
100
+ more_compute-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
+ more_compute-0.4.0.dist-info/entry_points.txt,sha256=xp7z9eRPNRM4oxkZZVlyXkhkSjN1AjoYI_B7qpDJ1bI,49
102
+ more_compute-0.4.0.dist-info/top_level.txt,sha256=Tamm6ADzjwaQa1z27O7Izcyhyt9f0gVjMv1_tC810aI,32
103
+ more_compute-0.4.0.dist-info/RECORD,,
@@ -1 +1 @@
1
- __version__ = "0.3.2"
1
+ __version__ = "0.4.0"
@@ -17,6 +17,7 @@ class NextZmqExecutor:
17
17
  error_utils: "ErrorUtils"
18
18
  cmd_addr: str
19
19
  pub_addr: str
20
+ ctrl_addr: str
20
21
  execution_count: int
21
22
  interrupt_timeout: float
22
23
  worker_pid: int | None
@@ -26,12 +27,14 @@ class NextZmqExecutor:
26
27
  ctx: object # zmq.Context - untyped due to zmq type limitations
27
28
  req: object # zmq.Socket - untyped due to zmq type limitations
28
29
  sub: object # zmq.Socket - untyped due to zmq type limitations
30
+ ctrl: object # zmq.Socket - control socket for interrupts
29
31
  is_remote: bool # Flag to track if connected to remote worker
30
32
 
31
33
  def __init__(self, error_utils: "ErrorUtils", cmd_addr: str | None = None, pub_addr: str | None = None, interrupt_timeout: float = 0.5) -> None:
32
34
  self.error_utils = error_utils
33
35
  self.cmd_addr = cmd_addr or os.getenv('MC_ZMQ_CMD_ADDR', 'tcp://127.0.0.1:5555')
34
36
  self.pub_addr = pub_addr or os.getenv('MC_ZMQ_PUB_ADDR', 'tcp://127.0.0.1:5556')
37
+ self.ctrl_addr = os.getenv('MC_ZMQ_CTRL_ADDR', self.cmd_addr.replace('5555', '5557'))
35
38
  self.execution_count = 0
36
39
  self.interrupt_timeout = interrupt_timeout
37
40
  self.worker_pid = None
@@ -46,6 +49,9 @@ class NextZmqExecutor:
46
49
  self.sub = self.ctx.socket(zmq.SUB) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
47
50
  self.sub.connect(self.pub_addr) # type: ignore[reportAttributeAccessIssue]
48
51
  self.sub.setsockopt_string(zmq.SUBSCRIBE, '') # type: ignore[reportAttributeAccessIssue]
52
+ # Control socket for interrupts (DEALER to connect to worker's ROUTER)
53
+ self.ctrl = self.ctx.socket(zmq.DEALER) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
54
+ self.ctrl.connect(self.ctrl_addr) # type: ignore[reportAttributeAccessIssue]
49
55
  self._ensure_worker()
50
56
 
51
57
  def _ensure_special_handler(self) -> None:
@@ -66,7 +72,9 @@ class NextZmqExecutor:
66
72
  try:
67
73
  tmp.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
68
74
  tmp.send_json({'type': 'ping'}) # type: ignore[reportAttributeAccessIssue]
69
- _ = cast(dict[str, object], tmp.recv_json()) # type: ignore[reportAttributeAccessIssue]
75
+ resp = cast(dict[str, object], tmp.recv_json()) # type: ignore[reportAttributeAccessIssue]
76
+ # Store worker PID even if already running (for force-kill)
77
+ self.worker_pid = resp.get('pid') # type: ignore[assignment]
70
78
  except Exception:
71
79
  #worker not responding, need to start it
72
80
  pass
@@ -80,12 +88,14 @@ class NextZmqExecutor:
80
88
  env = os.environ.copy()
81
89
  env.setdefault('MC_ZMQ_CMD_ADDR', self.cmd_addr)
82
90
  env.setdefault('MC_ZMQ_PUB_ADDR', self.pub_addr)
91
+ env.setdefault('MC_ZMQ_CTRL_ADDR', self.ctrl_addr)
83
92
  try:
84
93
  # Keep track of the worker process
85
94
  # Redirect stderr to see errors during development
86
95
  self.worker_proc = subprocess.Popen(
87
96
  [sys.executable, '-m', 'morecompute.execution.worker'],
88
97
  env=env,
98
+ stdin=subprocess.DEVNULL, # Explicitly close stdin to prevent fd issues
89
99
  stdout=subprocess.DEVNULL,
90
100
  stderr=None # Show errors in terminal
91
101
  )
@@ -147,8 +157,10 @@ class NextZmqExecutor:
147
157
  normalized_source, result, start_time, execution_count, websocket, cell_index
148
158
  )
149
159
  result['execution_time'] = f"{(time.time()-start_time)*1000:.1f}ms"
160
+ print(f"[EXECUTOR] Sending execution_complete for cell {cell_index}, status={result.get('status')}, has_error={result.get('error') is not None}", file=sys.stderr, flush=True)
150
161
  if websocket:
151
162
  await websocket.send_json({'type': 'execution_complete', 'data': {'cell_index': cell_index, 'result': result}})
163
+ print(f"[EXECUTOR] Sent execution_complete successfully", file=sys.stderr, flush=True)
152
164
  return result
153
165
  # For remote execution OR mixed commands, fall through to send via ZMQ
154
166
 
@@ -162,10 +174,18 @@ class NextZmqExecutor:
162
174
  # Consume pub until we see complete for this cell
163
175
  start_time = time.time()
164
176
  max_wait = 300.0 # 5 minute timeout for really long operations
177
+ interrupted_time = None # Track when interrupt was sent
165
178
  while True:
166
179
  # Check if this cell was interrupted
167
- if self.interrupted_cell == cell_index:
168
- print(f"[EXECUTE] Cell {cell_index} was interrupted, breaking out of execution loop", file=sys.stderr, flush=True)
180
+ if self.interrupted_cell == cell_index and interrupted_time is None:
181
+ print(f"[EXECUTE] Cell {cell_index} was interrupted, waiting for subprocess to be killed...", file=sys.stderr, flush=True)
182
+ interrupted_time = time.time()
183
+ # Don't break immediately - wait for execution_complete from worker
184
+ # Give worker 5 seconds to kill subprocess and send completion
185
+
186
+ # If interrupted and waited long enough, force break
187
+ if interrupted_time and (time.time() - interrupted_time > 5.0):
188
+ print(f"[EXECUTE] Cell {cell_index} interrupt timeout, breaking out", file=sys.stderr, flush=True)
169
189
  self.interrupted_cell = None # Clear the flag
170
190
  result.update({
171
191
  'status': 'error',
@@ -173,7 +193,7 @@ class NextZmqExecutor:
173
193
  'output_type': 'error',
174
194
  'ename': 'KeyboardInterrupt',
175
195
  'evalue': 'Execution interrupted by user',
176
- 'traceback': ['KeyboardInterrupt: Execution was stopped by user']
196
+ 'traceback': [] # No traceback needed for user-initiated interrupt
177
197
  }
178
198
  })
179
199
  break
@@ -214,6 +234,10 @@ class NextZmqExecutor:
214
234
  elif t == 'execution_complete' and msg.get('cell_index') == cell_index:
215
235
  result.update(msg.get('result') or {})
216
236
  result.setdefault('execution_count', execution_count)
237
+ # Clear interrupted flag if this was interrupted
238
+ if self.interrupted_cell == cell_index:
239
+ print(f"[EXECUTE] Cell {cell_index} completed after interrupt", file=sys.stderr, flush=True)
240
+ self.interrupted_cell = None
217
241
  break
218
242
 
219
243
  # Try to receive the reply from REQ socket (if worker is still alive)
@@ -247,7 +271,7 @@ class NextZmqExecutor:
247
271
  return result
248
272
 
249
273
  async def interrupt_kernel(self, cell_index: int | None = None) -> None:
250
- """Interrupt the kernel with escalation to force-kill if needed"""
274
+ """Interrupt the kernel using the control socket"""
251
275
  import sys
252
276
  print(f"[INTERRUPT] Starting interrupt for cell {cell_index}", file=sys.stderr, flush=True)
253
277
 
@@ -260,32 +284,22 @@ class NextZmqExecutor:
260
284
  if isinstance(cell_index, int):
261
285
  payload['cell_index'] = cell_index
262
286
 
263
- # Try graceful interrupt, but don't trust it for blocking I/O
287
+ # Send interrupt on control socket (non-blocking)
264
288
  try:
265
- # Very short timeout since we'll force-kill anyway
266
- self.req.setsockopt(zmq.SNDTIMEO, 100) # type: ignore[reportAttributeAccessIssue]
267
- self.req.setsockopt(zmq.RCVTIMEO, 100) # type: ignore[reportAttributeAccessIssue]
268
- self.req.send_json(payload) # type: ignore[reportAttributeAccessIssue]
269
- _ = cast(dict[str, object], self.req.recv_json()) # type: ignore[reportAttributeAccessIssue]
270
- print(f"[INTERRUPT] Sent interrupt signal to worker", file=sys.stderr, flush=True)
289
+ self.ctrl.setsockopt(zmq.SNDTIMEO, 1000) # type: ignore[reportAttributeAccessIssue]
290
+ self.ctrl.setsockopt(zmq.RCVTIMEO, 1000) # type: ignore[reportAttributeAccessIssue]
291
+ self.ctrl.send_json(payload) # type: ignore[reportAttributeAccessIssue]
292
+ _ = cast(dict[str, object], self.ctrl.recv_json()) # type: ignore[reportAttributeAccessIssue]
293
+ print(f"[INTERRUPT] Sent interrupt signal to worker via control socket", file=sys.stderr, flush=True)
271
294
  except Exception as e:
272
295
  print(f"[INTERRUPT] Could not send interrupt signal: {e}", file=sys.stderr, flush=True)
296
+ # If control socket fails, try force-kill immediately
297
+ print(f"[INTERRUPT] Force killing worker immediately...", file=sys.stderr, flush=True)
298
+ await self._force_kill_worker()
273
299
  finally:
274
300
  # Reset timeouts
275
- self.req.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
276
- self.req.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
277
-
278
- # Wait briefly to see if worker responds, but DON'T read from pub socket
279
- # (execute_cell is already reading from it - we'd steal messages!)
280
- # Instead, just wait a moment and force-kill if needed
281
- print(f"[INTERRUPT] Waiting {self.interrupt_timeout}s before force-kill...", file=sys.stderr, flush=True)
282
- await asyncio.sleep(self.interrupt_timeout)
283
-
284
- # For blocking I/O operations, interrupt rarely works - just force-kill
285
- # The interrupted_cell flag will let execute_cell break out gracefully
286
- print(f"[INTERRUPT] Force killing worker to ensure stop...", file=sys.stderr, flush=True)
287
- await self._force_kill_worker()
288
- print(f"[INTERRUPT] Force kill completed", file=sys.stderr, flush=True)
301
+ self.ctrl.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
302
+ self.ctrl.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
289
303
 
290
304
  # Interrupt special handler
291
305
  if self.special_handler:
@@ -324,13 +338,16 @@ class NextZmqExecutor:
324
338
  # CRITICAL: Reset socket state - close and recreate
325
339
  # The REQ socket may be waiting for a reply from the dead worker
326
340
  try:
327
- print(f"[FORCE_KILL] Resetting REQ socket", file=sys.stderr, flush=True)
341
+ print(f"[FORCE_KILL] Resetting REQ and CTRL sockets", file=sys.stderr, flush=True)
328
342
  self.req.close(0) # type: ignore[reportAttributeAccessIssue]
329
343
  self.req = self.ctx.socket(zmq.REQ) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
330
344
  self.req.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
331
- print(f"[FORCE_KILL] REQ socket reset complete", file=sys.stderr, flush=True)
345
+ self.ctrl.close(0) # type: ignore[reportAttributeAccessIssue]
346
+ self.ctrl = self.ctx.socket(zmq.DEALER) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
347
+ self.ctrl.connect(self.ctrl_addr) # type: ignore[reportAttributeAccessIssue]
348
+ print(f"[FORCE_KILL] Socket reset complete", file=sys.stderr, flush=True)
332
349
  except Exception as e:
333
- print(f"[FORCE_KILL] Error resetting socket: {e}", file=sys.stderr, flush=True)
350
+ print(f"[FORCE_KILL] Error resetting sockets: {e}", file=sys.stderr, flush=True)
334
351
 
335
352
  # Respawn worker
336
353
  try:
@@ -340,40 +357,86 @@ class NextZmqExecutor:
340
357
 
341
358
  def reset_kernel(self) -> None:
342
359
  """Reset the kernel by shutting down worker and restarting"""
343
- # Try graceful shutdown first
360
+ import sys
361
+ print(f"[RESET] Starting kernel reset, worker_pid={self.worker_pid}, is_remote={self.is_remote}", file=sys.stderr, flush=True)
362
+
363
+ # If connected to remote GPU, DON'T kill the worker - just send shutdown message
364
+ if self.is_remote:
365
+ print(f"[RESET] Remote worker - sending shutdown message only", file=sys.stderr, flush=True)
366
+ try:
367
+ self.req.setsockopt(zmq.SNDTIMEO, 2000) # type: ignore[reportAttributeAccessIssue]
368
+ self.req.setsockopt(zmq.RCVTIMEO, 2000) # type: ignore[reportAttributeAccessIssue]
369
+ self.req.send_json({'type': 'shutdown'}) # type: ignore[reportAttributeAccessIssue]
370
+ _ = cast(dict[str, object], self.req.recv_json()) # type: ignore[reportAttributeAccessIssue]
371
+ print(f"[RESET] Remote worker acknowledged shutdown", file=sys.stderr, flush=True)
372
+ except Exception as e:
373
+ print(f"[RESET] Remote worker shutdown failed: {e}", file=sys.stderr, flush=True)
374
+ finally:
375
+ self.req.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
376
+ self.req.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
377
+
378
+ # Reset execution count but don't respawn worker
379
+ self.execution_count = 0
380
+ print(f"[RESET] Remote kernel reset complete", file=sys.stderr, flush=True)
381
+ return
382
+
383
+ # Local worker mode - try graceful shutdown first
344
384
  try:
345
385
  self.req.setsockopt(zmq.SNDTIMEO, 500) # type: ignore[reportAttributeAccessIssue]
346
386
  self.req.setsockopt(zmq.RCVTIMEO, 500) # type: ignore[reportAttributeAccessIssue]
347
387
  self.req.send_json({'type': 'shutdown'}) # type: ignore[reportAttributeAccessIssue]
348
388
  _ = cast(dict[str, object], self.req.recv_json()) # type: ignore[reportAttributeAccessIssue]
349
- except Exception:
350
- pass
389
+ print(f"[RESET] Sent graceful shutdown message", file=sys.stderr, flush=True)
390
+ except Exception as e:
391
+ print(f"[RESET] Graceful shutdown failed: {e}", file=sys.stderr, flush=True)
351
392
  finally:
352
393
  self.req.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
353
394
  self.req.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
354
395
 
355
- # Force kill if needed
396
+ # Force kill local worker if needed
356
397
  if self.worker_pid:
357
398
  try:
399
+ print(f"[RESET] Sending SIGTERM to worker PID {self.worker_pid}", file=sys.stderr, flush=True)
358
400
  os.kill(self.worker_pid, signal.SIGTERM)
359
- time.sleep(0.2)
401
+ time.sleep(0.3) # Give it time to shutdown gracefully
360
402
  try:
403
+ # Check if still alive
361
404
  os.kill(self.worker_pid, 0)
405
+ # Still alive, force kill
406
+ print(f"[RESET] Worker still alive, sending SIGKILL", file=sys.stderr, flush=True)
362
407
  os.kill(self.worker_pid, signal.SIGKILL)
408
+ time.sleep(0.2) # Wait for SIGKILL to complete
363
409
  except ProcessLookupError:
364
- pass
365
- except Exception:
366
- pass
410
+ print(f"[RESET] Worker process terminated", file=sys.stderr, flush=True)
411
+ except Exception as e:
412
+ print(f"[RESET] Error killing worker: {e}", file=sys.stderr, flush=True)
367
413
 
368
414
  if self.worker_proc:
369
415
  try:
370
416
  self.worker_proc.terminate()
371
417
  self.worker_proc.wait(timeout=1)
418
+ print(f"[RESET] Worker process terminated via Popen", file=sys.stderr, flush=True)
372
419
  except Exception:
373
420
  try:
374
421
  self.worker_proc.kill()
375
- except Exception:
376
- pass
422
+ print(f"[RESET] Worker process killed via Popen", file=sys.stderr, flush=True)
423
+ except Exception as e:
424
+ print(f"[RESET] Error killing via Popen: {e}", file=sys.stderr, flush=True)
425
+
426
+ # Close sockets first, BEFORE recreating them
427
+ print(f"[RESET] Closing old sockets", file=sys.stderr, flush=True)
428
+ try:
429
+ self.req.close(0) # type: ignore[reportAttributeAccessIssue]
430
+ except Exception:
431
+ pass
432
+ try:
433
+ self.ctrl.close(0) # type: ignore[reportAttributeAccessIssue]
434
+ except Exception:
435
+ pass
436
+
437
+ # Wait for ZMQ to release the sockets (critical!)
438
+ time.sleep(0.5)
439
+ print(f"[RESET] Sockets closed, waited for cleanup", file=sys.stderr, flush=True)
377
440
 
378
441
  # Reset state
379
442
  self.execution_count = 0
@@ -382,11 +445,14 @@ class NextZmqExecutor:
382
445
 
383
446
  # Recreate sockets
384
447
  try:
385
- self.req.close(0) # type: ignore[reportAttributeAccessIssue]
448
+ print(f"[RESET] Creating new sockets", file=sys.stderr, flush=True)
386
449
  self.req = self.ctx.socket(zmq.REQ) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
387
450
  self.req.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
388
- except Exception:
389
- pass
451
+ self.ctrl = self.ctx.socket(zmq.DEALER) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
452
+ self.ctrl.connect(self.ctrl_addr) # type: ignore[reportAttributeAccessIssue]
453
+ print(f"[RESET] New sockets created successfully", file=sys.stderr, flush=True)
454
+ except Exception as e:
455
+ print(f"[RESET] Error creating sockets: {e}", file=sys.stderr, flush=True)
390
456
 
391
457
  # Reset special handler
392
458
  if self.special_handler is not None:
@@ -394,6 +460,10 @@ class NextZmqExecutor:
394
460
 
395
461
  # Respawn worker
396
462
  try:
463
+ print(f"[RESET] Respawning new worker", file=sys.stderr, flush=True)
397
464
  self._ensure_worker()
398
- except Exception:
399
- pass
465
+ print(f"[RESET] Kernel reset complete, new worker_pid={self.worker_pid}", file=sys.stderr, flush=True)
466
+ except Exception as e:
467
+ print(f"[RESET] Error respawning worker: {e}", file=sys.stderr, flush=True)
468
+ import traceback
469
+ traceback.print_exc()