diff --git a/src/web/web.go b/src/web/web.go index 2151cec07a6b25441548a176e299635c8098c291..e767fb1c81f1137e38c5faa93290af101fed605b 100644 --- a/src/web/web.go +++ b/src/web/web.go @@ -87,6 +87,8 @@ func new_mother_proc(binpath string, lib string) (*MotherProcess, error) { debugf("Debug: Loaded file") } + // start the lisp forking loop, which calls a functino from a c library + // that loops over stdin input and forks a new process for each line _, err = fmt.Fprint(in_pipe_r, ":lisp (maxima-fork:forking-loop)\n") if err != nil { return nil, fmt.Errorf("cannot send command to process: %s", err) @@ -180,9 +182,11 @@ func (p *MotherProcess) spawn_new(user *User) (*ChildProcess, float64, error) { }, float64(total.Microseconds())/1000, nil } +// takes a child process and evaluates a maxima command in it, while timing out if timeout is reached func (p *ChildProcess) eval_command(command string, timeout uint64) (*bytes.Buffer, float64, error) { start := time.Now() in_err := make(chan error, 1) + // write to stdin in separate goroutine to prevent deadlocks go func() { p.Input.SetWriteDeadline(time.Now().Add(time.Duration(timeout)*time.Millisecond)) _, err := io.Copy(p.Input, strings.NewReader(command)) @@ -190,6 +194,7 @@ func (p *ChildProcess) eval_command(command string, timeout uint64) (*bytes.Buff in_err<-err }() var outbuf bytes.Buffer + // read from stdout p.Outfile.SetReadDeadline(time.Now().Add(time.Duration(timeout)*time.Millisecond)) _, err := io.Copy(&outbuf, p.Output) p.Outfile.Close() @@ -210,6 +215,7 @@ func write_500(w http.ResponseWriter) { fmt.Fprint(w, "500 - internal server error\n") } +// kills all processes of user and remove temporary directories func process_cleanup(user *User, user_queue chan<- *User, tmp_dir string) { defer os.RemoveAll(tmp_dir) defer func() {user_queue <- user}() @@ -396,6 +402,7 @@ func get_env_number_positive(varname string, def uint) (uint, error) { } func main() { + // register/initialize various prometheus metrics metrics := Metrics { ResponseTime: prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "maxima_response_time", @@ -428,18 +435,23 @@ func main() { if len(os.Args) != 2 { log.Fatal("Fatal: wrong cli-argument usage: web [path to maxima executable]") } + // number of maxima users user_number, err := get_env_number_positive("GOEMAXIMA_NUSER", 16) if err != nil { log.Fatal("Fatal: GOEMAXIMA_NUSER contains invalid number"); } + // length of queue of ready maxima processes (-1) queue_len, err := get_env_number_positive("GOEMAXIMA_QUEUE_LEN", 3) if err != nil { log.Fatal("Fatal: GOEMAXIMA_QUEUE_LEN contains invalid number"); } + // enable debug messages debug, err = get_env_number_positive("GOEMAXIMA_DEBUG", 0) if err != nil { log.Fatal("Fatal: GOEMAXIMA_DEBUG contains invalid number"); } + // where to store temp files (plots, named pipes) + // should preferrably be tmpfs since it needs to be fast tmp_prefix = os.Getenv("GOEMAXIMA_TEMP_DIR") if tmp_prefix == "" { tmp_prefix = "/tmp/maxima" @@ -451,8 +463,12 @@ func main() { log.Fatalf("Fatal: Cannot create %s: %s", tmp_prefix, err) } + // queue of ready maxima processes queue := make(chan *ChildProcess, queue_len) + // queue of available user ids user_queue := make(chan *User, user_number) + + // look up all the users for i := (uint)(1); i <= user_number; i++ { user_name := fmt.Sprintf("maxima-%d", i) user, err := user.Lookup(user_name) @@ -474,7 +490,10 @@ func main() { Gid: gid, } } + + // spawn maxima processes in separate goroutine go generate_maximas(os.Args[1], os.Getenv("GOEMAXIMA_LIB_PATH"), queue, user_queue, &metrics) + http.Handle("/metrics", promhttp.Handler()) http.HandleFunc("/maxima/", func (w http.ResponseWriter, r *http.Request) {