more-compute 0.3.3__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.
- frontend/app/globals.css +9 -7
- frontend/app/layout.tsx +43 -1
- frontend/components/Notebook.tsx +22 -1
- frontend/components/cell/CellButton.tsx +5 -4
- frontend/components/cell/MonacoCell.tsx +42 -21
- frontend/components/output/ErrorDisplay.tsx +14 -1
- frontend/contexts/PodWebSocketContext.tsx +1 -1
- frontend/lib/websocket.ts +2 -2
- frontend/next.config.mjs +2 -2
- kernel_run.py +107 -17
- {more_compute-0.3.3.dist-info → more_compute-0.4.0.dist-info}/METADATA +30 -28
- {more_compute-0.3.3.dist-info → more_compute-0.4.0.dist-info}/RECORD +25 -24
- morecompute/__version__.py +1 -1
- morecompute/execution/executor.py +113 -44
- morecompute/execution/worker.py +319 -107
- morecompute/notebook.py +65 -6
- morecompute/server.py +72 -40
- morecompute/utils/cell_magics.py +35 -4
- morecompute/utils/notebook_converter.py +129 -0
- morecompute/utils/py_percent_parser.py +190 -0
- morecompute/utils/special_commands.py +126 -49
- frontend/.DS_Store +0 -0
- {more_compute-0.3.3.dist-info → more_compute-0.4.0.dist-info}/WHEEL +0 -0
- {more_compute-0.3.3.dist-info → more_compute-0.4.0.dist-info}/entry_points.txt +0 -0
- {more_compute-0.3.3.dist-info → more_compute-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {more_compute-0.3.3.dist-info → more_compute-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: more-compute
|
|
3
|
-
Version: 0.
|
|
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,16 +44,12 @@ Dynamic: requires-python
|
|
|
44
44
|
[](https://www.python.org/downloads/)
|
|
45
45
|
[](LICENSE)
|
|
46
46
|
|
|
47
|
-
An interactive notebook environment, similar to Marimo and Google Colab, that runs locally.
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
An interactive Python notebook environment, similar to Marimo and Google Colab, that runs locally.
|
|
50
48
|
|
|
51
49
|
|
|
52
50
|
https://github.com/user-attachments/assets/8c7ec716-dade-4de2-ad37-71d328129c97
|
|
53
51
|
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
53
|
## Installation
|
|
58
54
|
|
|
59
55
|
**Prerequisites:** [Node.js](https://nodejs.org/) >= 20.10.0 required for web interface
|
|
@@ -83,40 +79,46 @@ pip install more-compute
|
|
|
83
79
|
## Usage
|
|
84
80
|
|
|
85
81
|
```bash
|
|
86
|
-
more-compute notebook.
|
|
87
|
-
more-compute
|
|
82
|
+
more-compute notebook.py # Open existing notebook
|
|
83
|
+
more-compute new # Create new notebook
|
|
88
84
|
more-compute --debug # Show logs
|
|
89
85
|
```
|
|
90
86
|
|
|
91
|
-
Opens automatically at http://localhost:
|
|
87
|
+
Opens automatically at http://localhost:3141
|
|
92
88
|
|
|
93
|
-
|
|
89
|
+
### Converting Between Formats
|
|
94
90
|
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
uv tool update-shell # Fixes PATH automatically
|
|
98
|
-
```
|
|
91
|
+
MoreCompute uses `.py` notebooks with `# %%` cell markers, but you can convert to/from `.ipynb`:
|
|
99
92
|
|
|
100
|
-
**
|
|
93
|
+
**From .ipynb to .py:**
|
|
101
94
|
```bash
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
```
|
|
95
|
+
# Auto-detect output name (notebook.ipynb -> notebook.py)
|
|
96
|
+
more-compute convert notebook.ipynb
|
|
105
97
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# 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
|
|
112
103
|
```
|
|
113
104
|
|
|
114
|
-
|
|
105
|
+
The converter automatically extracts dependencies from `!pip install` commands and adds UV inline script metadata.
|
|
106
|
+
|
|
107
|
+
**From .py to .ipynb:**
|
|
115
108
|
```bash
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
118
114
|
```
|
|
119
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
|
+
|
|
120
122
|
## Development
|
|
121
123
|
|
|
122
124
|
```bash
|
|
@@ -125,7 +127,7 @@ cd MORECOMPUTE
|
|
|
125
127
|
uv venv && source .venv/bin/activate
|
|
126
128
|
uv pip install -e .
|
|
127
129
|
cd frontend && npm install && cd ..
|
|
128
|
-
|
|
130
|
+
more-compute notebook.py
|
|
129
131
|
```
|
|
130
132
|
|
|
131
133
|
## License
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
kernel_run.py,sha256=
|
|
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=
|
|
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=
|
|
18
|
-
frontend/app/layout.tsx,sha256=
|
|
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=
|
|
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=
|
|
23
|
-
frontend/components/cell/MonacoCell.tsx,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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,17 +63,17 @@ 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.
|
|
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=
|
|
68
|
+
morecompute/__version__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
|
|
70
69
|
morecompute/cli.py,sha256=kVvzvPBqF8xO6UuhU_-TBn99nKwJ405R2mAS6zU0KBc,734
|
|
71
|
-
morecompute/notebook.py,sha256=
|
|
70
|
+
morecompute/notebook.py,sha256=RubIXps925vCFgHznkW0QvvW0I11p6IGDDOrhnZ4jYs,6824
|
|
72
71
|
morecompute/process_worker.py,sha256=KsE3r-XpkYGuyO4w3t54VKkD51LfNHAZc3TYattMtrg,7185
|
|
73
|
-
morecompute/server.py,sha256=
|
|
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=
|
|
77
|
-
morecompute/execution/worker.py,sha256
|
|
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
|
|
@@ -85,18 +84,20 @@ morecompute/services/prime_intellect.py,sha256=b705rHv3RPRsgWedRlHwoP_S-TxxZtMSy
|
|
|
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=
|
|
87
|
+
morecompute/utils/cell_magics.py,sha256=YLxltTBprCA8jfgsjqf7Shnk4NCmGKIp3aV5_CYkKRM,29072
|
|
89
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=
|
|
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.
|
|
99
|
-
more_compute-0.
|
|
100
|
-
more_compute-0.
|
|
101
|
-
more_compute-0.
|
|
102
|
-
more_compute-0.
|
|
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,,
|
morecompute/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
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
|
-
|
|
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,6 +88,7 @@ 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
|
|
@@ -148,8 +157,10 @@ class NextZmqExecutor:
|
|
|
148
157
|
normalized_source, result, start_time, execution_count, websocket, cell_index
|
|
149
158
|
)
|
|
150
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)
|
|
151
161
|
if websocket:
|
|
152
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)
|
|
153
164
|
return result
|
|
154
165
|
# For remote execution OR mixed commands, fall through to send via ZMQ
|
|
155
166
|
|
|
@@ -163,10 +174,18 @@ class NextZmqExecutor:
|
|
|
163
174
|
# Consume pub until we see complete for this cell
|
|
164
175
|
start_time = time.time()
|
|
165
176
|
max_wait = 300.0 # 5 minute timeout for really long operations
|
|
177
|
+
interrupted_time = None # Track when interrupt was sent
|
|
166
178
|
while True:
|
|
167
179
|
# Check if this cell was interrupted
|
|
168
|
-
if self.interrupted_cell == cell_index:
|
|
169
|
-
print(f"[EXECUTE] Cell {cell_index} was interrupted,
|
|
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)
|
|
170
189
|
self.interrupted_cell = None # Clear the flag
|
|
171
190
|
result.update({
|
|
172
191
|
'status': 'error',
|
|
@@ -174,7 +193,7 @@ class NextZmqExecutor:
|
|
|
174
193
|
'output_type': 'error',
|
|
175
194
|
'ename': 'KeyboardInterrupt',
|
|
176
195
|
'evalue': 'Execution interrupted by user',
|
|
177
|
-
'traceback': [
|
|
196
|
+
'traceback': [] # No traceback needed for user-initiated interrupt
|
|
178
197
|
}
|
|
179
198
|
})
|
|
180
199
|
break
|
|
@@ -215,6 +234,10 @@ class NextZmqExecutor:
|
|
|
215
234
|
elif t == 'execution_complete' and msg.get('cell_index') == cell_index:
|
|
216
235
|
result.update(msg.get('result') or {})
|
|
217
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
|
|
218
241
|
break
|
|
219
242
|
|
|
220
243
|
# Try to receive the reply from REQ socket (if worker is still alive)
|
|
@@ -248,7 +271,7 @@ class NextZmqExecutor:
|
|
|
248
271
|
return result
|
|
249
272
|
|
|
250
273
|
async def interrupt_kernel(self, cell_index: int | None = None) -> None:
|
|
251
|
-
"""Interrupt the kernel
|
|
274
|
+
"""Interrupt the kernel using the control socket"""
|
|
252
275
|
import sys
|
|
253
276
|
print(f"[INTERRUPT] Starting interrupt for cell {cell_index}", file=sys.stderr, flush=True)
|
|
254
277
|
|
|
@@ -261,32 +284,22 @@ class NextZmqExecutor:
|
|
|
261
284
|
if isinstance(cell_index, int):
|
|
262
285
|
payload['cell_index'] = cell_index
|
|
263
286
|
|
|
264
|
-
#
|
|
287
|
+
# Send interrupt on control socket (non-blocking)
|
|
265
288
|
try:
|
|
266
|
-
#
|
|
267
|
-
self.
|
|
268
|
-
self.
|
|
269
|
-
self.
|
|
270
|
-
|
|
271
|
-
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)
|
|
272
294
|
except Exception as e:
|
|
273
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()
|
|
274
299
|
finally:
|
|
275
300
|
# Reset timeouts
|
|
276
|
-
self.
|
|
277
|
-
self.
|
|
278
|
-
|
|
279
|
-
# Wait briefly to see if worker responds, but DON'T read from pub socket
|
|
280
|
-
# (execute_cell is already reading from it - we'd steal messages!)
|
|
281
|
-
# Instead, just wait a moment and force-kill if needed
|
|
282
|
-
print(f"[INTERRUPT] Waiting {self.interrupt_timeout}s before force-kill...", file=sys.stderr, flush=True)
|
|
283
|
-
await asyncio.sleep(self.interrupt_timeout)
|
|
284
|
-
|
|
285
|
-
# For blocking I/O operations, interrupt rarely works - just force-kill
|
|
286
|
-
# The interrupted_cell flag will let execute_cell break out gracefully
|
|
287
|
-
print(f"[INTERRUPT] Force killing worker to ensure stop...", file=sys.stderr, flush=True)
|
|
288
|
-
await self._force_kill_worker()
|
|
289
|
-
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]
|
|
290
303
|
|
|
291
304
|
# Interrupt special handler
|
|
292
305
|
if self.special_handler:
|
|
@@ -325,13 +338,16 @@ class NextZmqExecutor:
|
|
|
325
338
|
# CRITICAL: Reset socket state - close and recreate
|
|
326
339
|
# The REQ socket may be waiting for a reply from the dead worker
|
|
327
340
|
try:
|
|
328
|
-
print(f"[FORCE_KILL] Resetting REQ
|
|
341
|
+
print(f"[FORCE_KILL] Resetting REQ and CTRL sockets", file=sys.stderr, flush=True)
|
|
329
342
|
self.req.close(0) # type: ignore[reportAttributeAccessIssue]
|
|
330
343
|
self.req = self.ctx.socket(zmq.REQ) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
|
331
344
|
self.req.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
|
|
332
|
-
|
|
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)
|
|
333
349
|
except Exception as e:
|
|
334
|
-
print(f"[FORCE_KILL] Error resetting
|
|
350
|
+
print(f"[FORCE_KILL] Error resetting sockets: {e}", file=sys.stderr, flush=True)
|
|
335
351
|
|
|
336
352
|
# Respawn worker
|
|
337
353
|
try:
|
|
@@ -341,40 +357,86 @@ class NextZmqExecutor:
|
|
|
341
357
|
|
|
342
358
|
def reset_kernel(self) -> None:
|
|
343
359
|
"""Reset the kernel by shutting down worker and restarting"""
|
|
344
|
-
|
|
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
|
|
345
384
|
try:
|
|
346
385
|
self.req.setsockopt(zmq.SNDTIMEO, 500) # type: ignore[reportAttributeAccessIssue]
|
|
347
386
|
self.req.setsockopt(zmq.RCVTIMEO, 500) # type: ignore[reportAttributeAccessIssue]
|
|
348
387
|
self.req.send_json({'type': 'shutdown'}) # type: ignore[reportAttributeAccessIssue]
|
|
349
388
|
_ = cast(dict[str, object], self.req.recv_json()) # type: ignore[reportAttributeAccessIssue]
|
|
350
|
-
|
|
351
|
-
|
|
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)
|
|
352
392
|
finally:
|
|
353
393
|
self.req.setsockopt(zmq.SNDTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
|
|
354
394
|
self.req.setsockopt(zmq.RCVTIMEO, -1) # type: ignore[reportAttributeAccessIssue]
|
|
355
395
|
|
|
356
|
-
# Force kill if needed
|
|
396
|
+
# Force kill local worker if needed
|
|
357
397
|
if self.worker_pid:
|
|
358
398
|
try:
|
|
399
|
+
print(f"[RESET] Sending SIGTERM to worker PID {self.worker_pid}", file=sys.stderr, flush=True)
|
|
359
400
|
os.kill(self.worker_pid, signal.SIGTERM)
|
|
360
|
-
time.sleep(0.
|
|
401
|
+
time.sleep(0.3) # Give it time to shutdown gracefully
|
|
361
402
|
try:
|
|
403
|
+
# Check if still alive
|
|
362
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)
|
|
363
407
|
os.kill(self.worker_pid, signal.SIGKILL)
|
|
408
|
+
time.sleep(0.2) # Wait for SIGKILL to complete
|
|
364
409
|
except ProcessLookupError:
|
|
365
|
-
|
|
366
|
-
except Exception:
|
|
367
|
-
|
|
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)
|
|
368
413
|
|
|
369
414
|
if self.worker_proc:
|
|
370
415
|
try:
|
|
371
416
|
self.worker_proc.terminate()
|
|
372
417
|
self.worker_proc.wait(timeout=1)
|
|
418
|
+
print(f"[RESET] Worker process terminated via Popen", file=sys.stderr, flush=True)
|
|
373
419
|
except Exception:
|
|
374
420
|
try:
|
|
375
421
|
self.worker_proc.kill()
|
|
376
|
-
|
|
377
|
-
|
|
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)
|
|
378
440
|
|
|
379
441
|
# Reset state
|
|
380
442
|
self.execution_count = 0
|
|
@@ -383,11 +445,14 @@ class NextZmqExecutor:
|
|
|
383
445
|
|
|
384
446
|
# Recreate sockets
|
|
385
447
|
try:
|
|
386
|
-
|
|
448
|
+
print(f"[RESET] Creating new sockets", file=sys.stderr, flush=True)
|
|
387
449
|
self.req = self.ctx.socket(zmq.REQ) # type: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
|
388
450
|
self.req.connect(self.cmd_addr) # type: ignore[reportAttributeAccessIssue]
|
|
389
|
-
|
|
390
|
-
|
|
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)
|
|
391
456
|
|
|
392
457
|
# Reset special handler
|
|
393
458
|
if self.special_handler is not None:
|
|
@@ -395,6 +460,10 @@ class NextZmqExecutor:
|
|
|
395
460
|
|
|
396
461
|
# Respawn worker
|
|
397
462
|
try:
|
|
463
|
+
print(f"[RESET] Respawning new worker", file=sys.stderr, flush=True)
|
|
398
464
|
self._ensure_worker()
|
|
399
|
-
|
|
400
|
-
|
|
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()
|