Skip to content
Snippets Groups Projects
Commit fafb4ad0 authored by lennart's avatar lennart
Browse files

implement faster fork-based way

parent c39d5b40
No related branches found
No related tags found
No related merge requests found
...@@ -4,11 +4,8 @@ FROM debian:stable ...@@ -4,11 +4,8 @@ FROM debian:stable
ARG MAXIMA_VERSION ARG MAXIMA_VERSION
# e.g. 2.0.22.0.2 # e.g. 2.0.22.0.2
ARG SBCL_VERSION ARG SBCL_VERSION
# e.g. assStackQuestion/classes/stack/maxima
ARG LIB_PATH
RUN echo ${LIB_PATH?Error \$LIB_PATH is not defined} \ RUN echo ${MAXIMA_VERSION?Error \$MAXIMA_VERSION is not defined} \
${MAXIMA_VERSION?Error \$MAXIMA_VERSION is not defined} \
${SBCL_VERSION?Error \$SBCL_VERSION is not defined} ${SBCL_VERSION?Error \$SBCL_VERSION is not defined}
ENV SRC=/opt/src \ ENV SRC=/opt/src \
...@@ -28,6 +25,7 @@ RUN apt-get update \ ...@@ -28,6 +25,7 @@ RUN apt-get update \
make \ make \
wget \ wget \
python3 \ python3 \
gcc \
# ca-certificates \ # ca-certificates \
# curl \ # curl \
texinfo texinfo
...@@ -55,16 +53,28 @@ RUN cd ${SRC} \ ...@@ -55,16 +53,28 @@ RUN cd ${SRC} \
&& make install \ && make install \
&& make clean && make clean
RUN apt-get install -y gnuplot gettext-base sudo psmisc libbsd-dev tini
COPY ./src/maxima_fork.c ${SRC}
RUN cd ${SRC} && gcc -shared maxima_fork.c -lbsd -fPIC -Wall -Wextra -o libmaximafork.so \
&& mv libmaximafork.so /usr/lib
RUN rm -r ${SRC} /SBCL_ARCH RUN rm -r ${SRC} /SBCL_ARCH
RUN apt-get install -y gnuplot gettext-base sudo psmisc
RUN mkdir -p ${LIB} ${LOG} ${TMP} ${PLOT} ${ASSETS} ${BIN} RUN mkdir -p ${LIB} ${LOG} ${TMP} ${PLOT} ${ASSETS} ${BIN}
# e.g. assStackQuestion/classes/stack/maxima
ARG LIB_PATH
RUN echo ${LIB_PATH?Error \$LIB_PATH is not defined}
# Copy Libraries # Copy Libraries
COPY ${LIB_PATH} ${LIB} COPY ${LIB_PATH} ${LIB}
# Copy optimization scripts # Copy optimization scripts
COPY assets/optimize.mac.template assets/maximalocal.mac.template ${ASSETS}/ COPY assets/maxima-fork.lisp assets/optimize.mac.template assets/maximalocal.mac.template ${ASSETS}/
RUN grep stackmaximaversion ${LIB}/stackmaxima.mac | grep -oP "\d+" >> /opt/maxima/stackmaximaversion \ RUN grep stackmaximaversion ${LIB}/stackmaxima.mac | grep -oP "\d+" >> /opt/maxima/stackmaximaversion \
&& sh -c 'envsubst < ${ASSETS}/maximalocal.mac.template > ${ASSETS}/maximalocal.mac \ && sh -c 'envsubst < ${ASSETS}/maximalocal.mac.template > ${ASSETS}/maximalocal.mac \
...@@ -74,19 +84,14 @@ RUN grep stackmaximaversion ${LIB}/stackmaxima.mac | grep -oP "\d+" >> /opt/maxi ...@@ -74,19 +84,14 @@ RUN grep stackmaximaversion ${LIB}/stackmaxima.mac | grep -oP "\d+" >> /opt/maxi
&& maxima -b optimize.mac \ && maxima -b optimize.mac \
&& mv maxima-optimised ${BIN}/maxima-optimised && mv maxima-optimised ${BIN}/maxima-optimised
RUN apt-get purge -y wget python3 make bzip2 texinfo RUN apt-get purge -y wget python3 make bzip2 texinfo gcc
RUN useradd -M maxima-server && echo "Defaults lecture = always" > /etc/sudoers.d/maxima
RUN for i in $(seq 16); do \ RUN for i in $(seq 16); do \
useradd -M "maxima-$i" \ useradd -M "maxima-$i"; \
&& echo "maxima-server ALL = (maxima-$i) NOPASSWD: ${BIN}/wrapper" >> /etc/sudoers.d/maxima \
&& echo "maxima-server ALL = (root) NOPASSWD: /usr/bin/killall -9 -u maxima-$i" >> /etc/sudoers.d/maxima; \
done done
# Add go webserver # Add go webserver
COPY ./bin/web ${BIN}/goweb COPY ./bin/web ${BIN}/goweb
# Add wrapper
COPY ./bin/wrapper ${BIN}/wrapper
CMD ["su", "-c", "/opt/maxima/bin/goweb", "maxima-server"] CMD rm /dev/tty && exec tini ${BIN}/goweb
(cl:in-package "MAXIMA")
(defun set-tmp-dir-vars (tmp-dir)
(defparameter |$image_dir| (concatenate 'string tmp-dir "/output/"))
(defparameter |$MAXIMA_TEMPDIR| (concatenate 'string tmp-dir "/work/"))
nil)
(defparameter |$url_base| "!ploturl!")
(cl:defpackage "MAXIMA-FORK"
(:use "CL" "SB-ALIEN")
(:export "FORKING-LOOP"))
(cl:in-package "MAXIMA-FORK")
;;; load shared library
(load-shared-object "libmaximafork.so")
;;; define c function
(declaim (inline fork-new-process))
(define-alien-routine fork-new-process c-string)
;;; forking loop
(defun forking-loop ()
(finish-output)
(let ((tmp-dir (fork-new-process)))
(when (not tmp-dir)
(sb-ext:exit :code 1))
(maxima::set-tmp-dir-vars tmp-dir))
t)
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
https://github.com/maths/moodle-qtype_stack/blob/master/doc/en/CAS/Optimising_Maxima.md */ https://github.com/maths/moodle-qtype_stack/blob/master/doc/en/CAS/Optimising_Maxima.md */
load("${ASSETS}/maximalocal.mac"); load("${ASSETS}/maximalocal.mac");
load("${ASSETS}/maxima-fork.lisp");
load("${LIB}/stackmaxima.mac"); load("${LIB}/stackmaxima.mac");
load(stats); load(stats);
load(distrib); load(distrib);
......
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <pwd.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <bsd/unistd.h>
#include <limits.h>
#include <grp.h>
#define N_SLOT 16
#define RNOFILE 256
#define FILEPATH_LEN (PATH_MAX + 1)
char filepath[FILEPATH_LEN];
// inits a maxima process for web service:
// changes gid/uid to maxima-{slot}
// redirects input/output, creates temporary subdirectories
char *fork_new_process() {
fflush(stdout);
// send an S for Synchronization, so that
// the server process doesn't accidentally write into
// sbcl's buffer
// the server should not write anything before it has read this
write(STDOUT_FILENO, "S", 1);
// while the loop is running, the SIGCHLD handler
// is deactivated so that children are automatically reaped
// after that, it is again restored
struct sigaction old, new;
new.sa_handler = SIG_IGN;
sigemptyset(&new.sa_mask);
new.sa_flags = SA_NOCLDWAIT;
char *ret = NULL;
if (sigaction(SIGCHLD, &new, &old) == -1) {
perror("Could not set signal error for children");
return NULL;
}
// when sbcl spawns a child process through lisp, sbcl tries to close all
// filedescriptors until RLIMIT_NOFILE
// in docker containers, this is by standard quite high, so it takes long
// which is remediated here by setting it lower manually
struct rlimit nofile = { .rlim_cur = RNOFILE, .rlim_max = RNOFILE };
if (setrlimit(RLIMIT_NOFILE, &nofile) == -1) {
perror("Error setting rlimit_nofile");
sigaction(SIGCHLD, &old, NULL);
return NULL;
}
for (;;) {
// can't flush enough
fflush(stdout);
int slot;
// the slot number and temp directory is sent to the process
// over stdin in the format "%d%s", where %s can contain anything
// but newlines and musn't start with a number, which isn't a
// problem for absolute paths
if (scanf("%d", &slot) == EOF) {
if (errno != 0) {
perror("Error getting slot number from stdin");
ret = NULL;
}
break;
}
char *tempdir = fgets(filepath, FILEPATH_LEN, stdin);
if (!tempdir) {
perror("Error getting temp path name");
ret = NULL;
break;
}
// remove the last newline, if it exists
size_t last_char = strlen(tempdir) - 1;
if (tempdir[last_char] == '\n') {
tempdir[strlen(tempdir) - 1] = '\0';
}
// we fork the main process and use the child without execve
// this way, startup time is improved
pid_t pid = fork();
if (pid == -1) {
perror("Could not fork");
ret = NULL;
break;
}
if (pid != 0) {
continue;
}
if (chdir(tempdir) == -1) {
perror("Could not chdir to temporary directory");
ret = NULL;
break;
}
// redirect stdout to pipe
// note: open outpipe before inpipe to avoid deadlock
int outfd = open("outpipe", O_WRONLY);
if (outfd == -1) {
perror("Could not connect output pipe");
ret = NULL;
break;
}
if (dup2(outfd, STDOUT_FILENO) == -1) {
perror("Could not copy output file descriptor");
ret = NULL;
break;
}
// redirect stdin from pipe
int infd = open("inpipe", O_RDONLY);
if (infd == -1) {
perror("Could not create input pipe");
ret = NULL;
break;
}
if (dup2(infd, STDIN_FILENO) == -1) {
perror("Could not copy input file descriptor");
ret = NULL;
break;
}
// replace stdin with a new stream, for good measure
FILE *new_stdin = fdopen(STDIN_FILENO, "r");
if (!new_stdin) {
perror("Could not create stream from stdin");
ret = NULL;
break;
}
stdin = new_stdin;
// everything execpt std{in,out,err} is closed
// note: this is a function from libbsd
closefrom(3);
// get uid/gid from username
if (slot <= 0 || slot > N_SLOT) {
dprintf(STDERR_FILENO, "Invalid slot number: %d\n", slot);
ret = NULL;
break;
}
char username[16];
int len = snprintf(username, 15, "maxima-%d", slot);
if (len < 0 || len > 15) {
dprintf(STDERR_FILENO, "Internal error getting user name\n");
ret = NULL;
break;
}
struct passwd *userinfo = getpwnam(username);
if (!userinfo) {
dprintf(STDERR_FILENO, "Could not read user information for %s: %s\n",
username, strerror(errno));
ret = NULL;
break;
}
uid_t uid = userinfo->pw_uid;
gid_t gid = userinfo->pw_gid;
if (uid == 0 || gid == 0) {
dprintf(STDERR_FILENO, "Refusing to setuid/gid to root\n");
ret = NULL;
break;
}
// note: setgid should be executed before setuid when dropping from root
if (setgid(gid) == -1) {
perror("Could not set gid");
ret = NULL;
break;
}
// remove all aux groups
if (setgroups(0, NULL)) {
perror("Could not remove aux groups");
ret = NULL;
break;
}
// after this, we should be non-root
if (setuid(uid) == -1) {
perror("Could not set uid");
ret = NULL;
break;
}
// create temporary folders and files
if (mkdir("output", 0770) == -1) {
perror("Could not create output directory");
ret = NULL;
break;
}
if (mkdir("work", 0770) == -1) {
perror("Could not create work directory");
ret = NULL;
break;
}
ret = tempdir;
break;
}
// restore normal SIGCHLD handler
if (sigaction(SIGCHLD, &old, NULL) == -1) {
return NULL;
}
return ret;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment