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.
- frontend/app/globals.css +10 -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 +153 -18
- {more_compute-0.3.2.dist-info → more_compute-0.4.0.dist-info}/METADATA +34 -24
- {more_compute-0.3.2.dist-info → more_compute-0.4.0.dist-info}/RECORD +27 -26
- morecompute/__version__.py +1 -1
- morecompute/execution/executor.py +114 -44
- morecompute/execution/worker.py +319 -107
- morecompute/notebook.py +65 -6
- morecompute/server.py +76 -44
- morecompute/services/pod_manager.py +2 -2
- morecompute/utils/cell_magics.py +35 -4
- morecompute/utils/config_util.py +47 -31
- 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.2.dist-info → more_compute-0.4.0.dist-info}/WHEEL +0 -0
- {more_compute-0.3.2.dist-info → more_compute-0.4.0.dist-info}/entry_points.txt +0 -0
- {more_compute-0.3.2.dist-info → more_compute-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {more_compute-0.3.2.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,7 +44,11 @@ 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.
|
|
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.
|
|
79
|
-
more-compute
|
|
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:
|
|
87
|
+
Opens automatically at http://localhost:3141
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
### Converting Between Formats
|
|
86
90
|
|
|
87
|
-
|
|
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
|
-
**
|
|
93
|
+
**From .ipynb to .py:**
|
|
93
94
|
```bash
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
```
|
|
95
|
+
# Auto-detect output name (notebook.ipynb -> notebook.py)
|
|
96
|
+
more-compute convert notebook.ipynb
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
130
|
+
more-compute notebook.py
|
|
121
131
|
```
|
|
122
132
|
|
|
123
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,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.
|
|
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
|
|
81
80
|
morecompute/services/lsp_service.py,sha256=Le8ARImcg2P6oueF_14L8rStHOOseHruRTd_wfDVw7s,12237
|
|
82
|
-
morecompute/services/pod_manager.py,sha256=
|
|
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=
|
|
89
|
-
morecompute/utils/config_util.py,sha256=
|
|
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=
|
|
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,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,
|
|
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': [
|
|
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
|
|
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
|
-
#
|
|
287
|
+
# Send interrupt on control socket (non-blocking)
|
|
264
288
|
try:
|
|
265
|
-
#
|
|
266
|
-
self.
|
|
267
|
-
self.
|
|
268
|
-
self.
|
|
269
|
-
|
|
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.
|
|
276
|
-
self.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
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.
|
|
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
|
-
|
|
365
|
-
except Exception:
|
|
366
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
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
|
-
|
|
399
|
-
|
|
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()
|