@@ -13,6 +13,8 @@ import type { JupyterKernelInterface as JupyterKernel } from "@cocalc/jupyter/ty
1313import { is_object , len , uuid , trunc_middle } from "@cocalc/util/misc" ;
1414import { retry_until_success } from "@cocalc/util/async-utils" ;
1515import { kernel } from "@cocalc/jupyter/kernel" ;
16+ import { mkdir , unlink , writeFile } from "node:fs/promises" ;
17+ import { dirname , join } from "node:path" ;
1618import getLogger from "@cocalc/backend/logger" ;
1719export type { Limits } ;
1820
@@ -41,10 +43,17 @@ export async function jupyter_run_notebook(
4143 total_output : 0 ,
4244 } ;
4345
44- const name = notebook . metadata . kernelspec . name ;
45- let jupyter : JupyterKernel | undefined = undefined ;
46+ // path is random so it doesn't randomly conflict with
47+ // something else running at the same time.
48+ const path = join ( opts . path , `${ uuid ( ) } .ipynb` ) ;
49+ try {
50+ await mkdir ( dirname ( path ) , { recursive : true } ) ;
51+ await writeFile ( path , opts . ipynb ) ;
52+
53+ const name = notebook . metadata . kernelspec . name ;
54+ let jupyter : JupyterKernel | undefined = undefined ;
4655
47- /* We use retry_until_success to spawn the kernel, since
56+ /* We use retry_until_success to spawn the kernel, since
4857 it makes people's lives much easier if this works even
4958 if there is a temporary issue. Also, in testing, I've
5059 found that sometimes if you try to spawn two kernels at
@@ -53,74 +62,76 @@ export async function jupyter_run_notebook(
5362 just work around it since we want extra reliability
5463 anyways.
5564 */
56- async function init_jupyter0 ( ) : Promise < void > {
57- log ( "init_jupyter" , jupyter != null ) ;
58- jupyter ?. close ( ) ;
59- jupyter = undefined ;
60- // path is random so it doesn't randomly conflict with
61- // something else running at the same time.
62- const path = opts . path + `/${ uuid ( ) } .ipynb` ;
63- jupyter = kernel ( { name, path } ) ;
64- log ( "init_jupyter: spawning" ) ;
65- // for Python, we suppress all warnings
66- // they end up as stderr-output and hence would imply 0 points
67- const env = { PYTHONWARNINGS : "ignore" } ;
68- await jupyter . spawn ( { env } ) ;
69- log ( "init_jupyter: spawned" ) ;
70- }
65+ async function init_jupyter0 ( ) : Promise < void > {
66+ log ( "init_jupyter" , jupyter != null ) ;
67+ jupyter ?. close ( ) ;
68+ jupyter = undefined ;
69+ jupyter = kernel ( { name, path } ) ;
70+ log ( "init_jupyter: spawning" ) ;
71+ // for Python, we suppress all warnings
72+ // they end up as stderr-output and hence would imply 0 points
73+ const env = { PYTHONWARNINGS : "ignore" } ;
74+ await jupyter . spawn ( { env } ) ;
75+ log ( "init_jupyter: spawned" ) ;
76+ }
7177
72- async function init_jupyter ( ) : Promise < void > {
73- await retry_until_success ( {
74- f : init_jupyter0 ,
75- start_delay : 1000 ,
76- max_delay : 5000 ,
77- factor : 1.4 ,
78- max_time : 30000 ,
79- log : function ( ...args ) {
80- log ( "init_jupyter - retry_until_success" , ...args ) ;
81- } ,
82- } ) ;
83- }
78+ async function init_jupyter ( ) : Promise < void > {
79+ await retry_until_success ( {
80+ f : init_jupyter0 ,
81+ start_delay : 1000 ,
82+ max_delay : 5000 ,
83+ factor : 1.4 ,
84+ max_time : 30000 ,
85+ log : function ( ...args ) {
86+ log ( "init_jupyter - retry_until_success" , ...args ) ;
87+ } ,
88+ } ) ;
89+ }
8490
85- try {
86- log ( "init_jupyter..." ) ;
87- await init_jupyter ( ) ;
88- log ( "init_jupyter: done" ) ;
89- for ( const cell of notebook . cells ) {
90- try {
91- if ( jupyter == null ) {
92- log ( "BUG: jupyter==null" ) ;
93- throw Error ( "jupyter can't be null since it was initialized above" ) ;
94- }
95- log ( "run_cell..." ) ;
96- await run_cell ( jupyter , limits , cell ) ; // mutates cell by putting in outputs
97- log ( "run_cell: done" ) ;
98- } catch ( err ) {
99- // fatal error occured, e.g,. timeout, broken kernel, etc.
100- if ( cell . outputs == null ) {
101- cell . outputs = [ ] ;
102- }
103- cell . outputs . push ( { traceback : [ `${ err } ` ] } ) ;
104- if ( ! global_timeout_exceeded ( limits ) ) {
105- // close existing jupyter and spawn new one, so we can robustly run more cells.
106- // Obviously, only do this if we are not out of time.
107- log ( "timeout exceeded so restarting..." ) ;
108- await init_jupyter ( ) ;
109- log ( "timeout exceeded restart done" ) ;
91+ try {
92+ log ( "init_jupyter..." ) ;
93+ await init_jupyter ( ) ;
94+ log ( "init_jupyter: done" ) ;
95+ for ( const cell of notebook . cells ) {
96+ try {
97+ if ( jupyter == null ) {
98+ log ( "BUG: jupyter==null" ) ;
99+ throw Error ( "jupyter can't be null since it was initialized above" ) ;
100+ }
101+ log ( "run_cell..." ) ;
102+ await run_cell ( jupyter , limits , cell ) ; // mutates cell by putting in outputs
103+ log ( "run_cell: done" ) ;
104+ } catch ( err ) {
105+ // fatal error occured, e.g,. timeout, broken kernel, etc.
106+ if ( cell . outputs == null ) {
107+ cell . outputs = [ ] ;
108+ }
109+ cell . outputs . push ( { traceback : [ `${ err } ` ] } ) ;
110+ if ( ! global_timeout_exceeded ( limits ) ) {
111+ // close existing jupyter and spawn new one, so we can robustly run more cells.
112+ // Obviously, only do this if we are not out of time.
113+ log ( "timeout exceeded so restarting..." ) ;
114+ await init_jupyter ( ) ;
115+ log ( "timeout exceeded restart done" ) ;
116+ }
110117 }
111118 }
119+ } finally {
120+ log ( "in finally" ) ;
121+ if ( jupyter != null ) {
122+ log ( "jupyter != null so closing" ) ;
123+ // @ts -ignore
124+ jupyter . close ( ) ;
125+ jupyter = undefined ;
126+ }
112127 }
128+ log ( "returning result" ) ;
129+ return JSON . stringify ( notebook ) ;
113130 } finally {
114- log ( "in finally" ) ;
115- if ( jupyter != null ) {
116- log ( "jupyter != null so closing" ) ;
117- // @ts -ignore
118- jupyter . close ( ) ;
119- jupyter = undefined ;
120- }
131+ try {
132+ await unlink ( path ) ;
133+ } catch { }
121134 }
122- log ( "returning result" ) ;
123- return JSON . stringify ( notebook ) ;
124135}
125136
126137export async function run_cell (
0 commit comments