import { RunNotebookOptions } from "@cocalc/frontend/jupyter/nbgrader/api" ; import { JupyterNotebook } from "@cocalc/frontend/jupyter/nbgrader/autograde" ; import { is_object , len , uuid , trunc_middle } from "@cocalc/util/misc" ; import { retry_until_success } from "@cocalc/util/async-utils" ; import { kernel , JupyterKernel } from "../jupyter/jupyter" ; interface Limits { timeout_ms_per_cell : number ; max_output_per_cell : number ; timeout_ms : number ; max_output : number ; start_time : number ; total_output : number ; } function global_timeout_exceeded ( limits ) { return ( limits . timeout_ms != 0 && new Date ( ) . valueOf ( ) - limits . start_time >= limits . timeout_ms ) ; } export async function jupyter_run_notebook ( client , logger , opts ) { const log = ( ... args ) => { logger . debug ( "jupyter_run_notebook" , ... args ) ; } ; log ( trunc_middle ( JSON . stringify ( opts ) ) ) ; const notebook = JSON . parse ( opts . ipynb ) ; let limits = { timeout_ms : opts . limits ?. max_total_time_ms ?? 0 , timeout_ms_per_cell : opts . limits ?. max_time_per_cell_ms ?? 0 , max_output : opts . limits ?. max_output ?? 0 , max_output_per_cell : opts . limits ?. max_output_per_cell ?? 0 , start_time : new Date ( ) . valueOf ( ) , total_output : 0 , } ; const name = notebook . metadata . kernelspec . name ; let jupyter = undefined ; async function init_jupyter0 ( ) { log ( "init_jupyter" , jupyter != null ) ; jupyter ?. close ( ) ; jupyter = undefined ; const path = opts . path + `/${ uuid ( ) }.ipynb` ; jupyter = kernel ( { name , client , path } ) ; log ( "init_jupyter: spawning" ) ; const env = { PYTHONWARNINGS : "ignore" } ; await jupyter . spawn ( { env } ) ; log ( "init_jupyter: spawned" ) ; } async function init_jupyter ( ) { await retry_until_success ( { f : init_jupyter0 , start_delay : 1000 , max_delay : 5000 , factor : 1.4 , max_time : 30000 , log : function ( ... args ) { log ( "init_jupyter - retry_until_success" , ... args ) ; } , } ) ; } try { log ( "init_jupyter..." ) ; await init_jupyter ( ) ; log ( "init_jupyter: done" ) ; for ( const cell of notebook . cells ) { try { if ( jupyter == null ) { log ( "BUG: jupyter==null" ) ; throw Error ( "jupyter can't be null since it was initialized above" ) ; } log ( "run_cell..." ) ; await run_cell ( jupyter , limits , cell ) ; log ( "run_cell: done" ) ; } catch ( err ) { if ( cell . outputs == null ) { cell . outputs = [ ] ; } cell . outputs . push ( { traceback : [ `${ err }` ] } ) ; if ( ! global_timeout_exceeded ( limits ) ) { log ( "timeout exceeded so restarting..." ) ; await init_jupyter ( ) ; log ( "timeout exceeded restart done" ) ; } } } } finally { log ( "in finally" ) ; if ( jupyter != null ) { log ( "jupyter != null so closing" ) ; jupyter . close ( ) ; jupyter = undefined ; } } log ( "returning result" ) ; return JSON . stringify ( notebook ) ; } async function run_cell ( jupyter , limits , cell ) { if ( jupyter == null ) { throw Error ( "jupyter must be defined" ) ; } if ( global_timeout_exceeded ( limits ) ) { throw Error ( `Total time limit (=${ Math . round ( limits . timeout_ms / 1000 ) } seconds) exceeded` ) ; } if ( cell . cell_type != "code" ) { return ; } const code = cell . source . join ( "" ) ; if ( cell . outputs == null ) { cell . outputs = [ ] ; } const result = await jupyter . execute_code_now ( { code , timeout_ms : limits . timeout_ms_per_cell , } ) ; let cell_output_chars = 0 ; for ( const x of result ) { if ( x == null ) continue ; if ( x [ "msg_type" ] == "clear_output" ) { cell . outputs = [ ] ; } const mesg = x [ "content" ] ; if ( mesg == null ) continue ; if ( mesg . comm_id != null ) { continue ; } delete mesg . execution_state ; delete mesg . execution_count ; delete mesg . payload ; delete mesg . code ; delete mesg . status ; delete mesg . source ; for ( const k in mesg ) { const v = mesg [ k ] ; if ( is_object ( v ) && len ( v ) === 0 ) { delete mesg [ k ] ; } } if ( len ( mesg ) == 0 ) continue ; const n = JSON . stringify ( mesg ) . length ; limits . total_output += n ; if ( limits . max_output_per_cell ) { cell_output_chars += n ; } if ( mesg [ "traceback" ] != null ) { cell . outputs . push ( mesg ) ; } else { if ( limits . max_output_per_cell && cell_output_chars > limits . max_output_per_cell ) { cell . outputs . push ( { name : "stdout" , output_type : "stream" , text : [ `Output truncated since it exceeded the cell output limit of ${ limits . max_output_per_cell } characters` , ] , } ) ; } else if ( limits . max_output && limits . total_output > limits . max_output ) { cell . outputs . push ( { name : "stdout" , output_type : "stream" , text : [ `Output truncated since it exceeded the global output limit of ${ limits . max_output } characters` , ] , } ) ; } else { cell . outputs . push ( mesg ) ; } } } }