sbcl executable crash when running a hunchentoot server - http

I want to create a http server with sbcl + hunchentoot. Everything goes well as long as I test the codes in a REPL or run the script file directly, but when I save the core as an executable, the server just crashes every time I try to access the web page of the hunchentoot server.
The testing code is fairly easy, in the server.lisp file, we have:
#!/usr/local/bin/sbcl --script
(require :asdf)
(asdf:load-system :hunchentoot)
(defpackage :http-server
(:use :cl :hunchentoot))
(in-package :http-server)
(defvar *my-acceptor* (make-instance 'easy-acceptor :port 6161))
(defun main ()
(format t "Starting http server on port ~a~%" (acceptor-port *my-acceptor*))
(start *my-acceptor*)
(handler-case
(loop do (sleep 1000))
(condition () nil)))
(defun hello ()
(format nil "Hello, it works!"))
(push
(create-prefix-dispatcher "/hello.html" #'hello)
*dispatch-table*)
;; (sb-ext:save-lisp-and-die "http-server" :toplevel 'main :executable t)
(main)
Now, if I just run the script directly in the terminal, it works flawlessly. The hello.html page can be accessed. Then I change the last two lines to
(sb-ext:save-lisp-and-die "http-server" :toplevel 'main :executable t)
;; (main)
My intention is to run the script again, and it will generate an executable file http-server. Then we have problems.
I start the server by executing the executable file http-server, and every time I access "http://127.0.0.1:6161/hello.html", the http-server process just crashes, with following error messages:
Starting http server on port 6161
CORRUPTION WARNING in SBCL pid 206086 tid 206091:
Memory fault at 0x7f9c7c58b000 (pc=0x53617132 [code 0x53616f20+0x212 ID 0x43a3], fp=0x7f696af2dbc0, sp=0x7f696af2db68) tid 206091
The integrity of this image is possibly compromised.
Continuing with fingers crossed.
CORRUPTION WARNING in SBCL pid 206086 tid 206091:
Memory fault at 0x7f9c7c58b000 (pc=0x53617c03 [code 0x53617a30+0x1D3 ID 0x43a5], fp=0x7f696af2dc40, sp=0x7f696af2dc18) tid 206091
The integrity of this image is possibly compromised.
Continuing with fingers crossed.
Would some one please help me look into what is going on here? It just does not make sense the same codes work well in a REPL or run as a script, but do not work as an executable. Is there something wrong with how I generate the executable?
My OS is LinuxMint 21. sbcl version is 2.2.8, hunchentoot version is 1.3.0.
Many Thanks!
SOME UPDATES This issue can be easily reproduced with the latest SBCL 2.2.9 + latest hunchentoot 1.3.0. With a well prepared environment, just run the script and generate the executable file, and you are ready for the investigating. Since both SBCL and hunchentoot are very popular in the community, it just does not make sense we don't have an explanation of this problem.
ANOTHER UPDATE I change the method of generating the executable, and it WORKS this time. It turns out that I have to create the executable from a REPL, rather than use the script file above. I think it might relate to the shebang line, the "--script" will disable the ldb debugger, it is probably not an ideal way to generate executable. The lesson has been learned.
FINAL UPDATE Actually we still can generate the executable from a script file, as long as we don't use the "--script" option. For example, put these lines at the beginning of the script file, and it works (Remember remove the last two lines from the original script):
#|
# This is the correct way to generate the executable from this script file
sbcl --noinform --no-userinit --load "$0" --eval $'(sb-ext:save-lisp-and-die "http-server" :toplevel \'http-server::main :executable t)'
exit 0
|#

You could use Roswell.
Install Roswell following:
https://towardsdatascience.com/how-to-set-up-common-lisp-ide-in-2021-5be70d88975b
(I am the author of this article).
Generate a folder service and cd service into it.
I put your script into the file: service.lisp
(ql:quickload :hunchentoot)
(defpackage :http-server
(:use :cl :hunchentoot))
(in-package :http-server)
(defvar *my-acceptor* (make-instance 'easy-acceptor :port 6161))
(defun main ()
(format t "Starting http server on port ~a~%" (acceptor-port *my-acceptor*))
(start *my-acceptor*)
(handler-case
(loop do (sleep 1000))
(condition () nil)))
(defun hello ()
(format nil "Hello, it works!"))
(push
(create-prefix-dispatcher "/hello.html" #'hello)
*dispatch-table*)
;; (sb-ext:save-lisp-and-die "http-server" :toplevel 'main :executable t)
(main)
Then, I run
$ ros init service
Which generates a file service.ros Which content I let be:
#!/bin/sh
#|-*- mode:lisp -*-|#
#|
exec ros -Q -- $0 "$#"
|#
(progn ;;init forms
(ros:ensure-asdf)
#+quicklisp(ql:quickload '() :silent t)
)
(defpackage :ros.script.service.3874727090
(:use :cl))
(in-package :ros.script.service.3874727090)
(defun main (&rest argv)
(declare (ignorable argv))
(load "service.lisp")
(main))
;;; vim: set ft=lisp lisp:
Then, you have to install the script into roswell:
$ ros install service.ros
It will show a path. In my case sth like: ~/.roswell/bin/service.
There is a linux program called service already.
So I alias this: $ alias serveit="~/.roswell/bin/service"
Now I can fire: $ serveit
serveit
To load "hunchentoot":
Load 1 ASDF system:
hunchentoot
; Loading "hunchentoot"
....
Starting http server on port 6161
Roswell scripting is explained here:
https://roswell.github.io/Roswell-as-a-Scripting-Environment.html

Related

Common Lisp Executable doesn't do anything

I have followed all the steps in the cl-cookbook. I created a webapp project with quickprojects.
I have my .asd file:
(asdf:defsystem #:serve
:description "Describe server here"
:author "Your Name <your.name#example.com>"
:license "Specify license here"
:version "0.0.1"
:serial t
:depends-on (#:hunchentoot)
:components ((:file "package")
(:file "serve"))
:build-operation "program-op" ;; leave as is
:build-pathname "serve"
:entry-point "serve:main")
I have the lisp server file:
(ql:quickload "serve")
(in-package #:serve)
(defvar *acceptor* (make-instance 'hunchentoot:easy-acceptor :port 4242
:document-root #p"www/"))
;; start the server
(defun main ()
(hunchentoot:start *acceptor*))
(setf (hunchentoot:acceptor-document-root *acceptor*) #p"./www/")
I have the package.lisp file:
(defpackage #:serve
(:use #:cl)
(:export main))
Finally, I have the make file for creating the executable:
build:
sbcl \
--eval '(load "serve.asd")' \
--eval '(ql:quickload "serve")' \
--eval "(asdf:make :serve)" \
--eval '(quit)'
When the executable comes out after running make build, and I run it with ./serve.... nothing happens.
My expectation is that the server will run as it does when i run it with emacs.
My goal is to send the executable to the server to run it. Not sure if that is the best deploy process. But at the moment, it's good enough for testing.
Even more worrying is that when I send the executable to my ubuntu server, i get a zsh error. This only happens when I build it on my MacOs and send it to ubuntu. However, when I build on ubuntu (i rebuilt the project on the VM to test it), the executable does nothing.
Your main problem is that your main function immediately returns, terminating the server as soon as it is started. I would change it as follows:
;; start the server
(defun main ()
(hunchentoot:start *acceptor*)
(sb-impl::toplevel-init))
When you rebuild and run ./serve, a simple prompt appears *, which is the SBCL REPL. Your webserver is running in another thread. This can be useful to query the state of the server, or debug. You can even start a Swank server to connect and hack the code while it runs.
When you want to stop the server, press CTRL-D to exit the REPL and the program terminates.
You can imagine other mechanisms to keep the server running until everything stops (like joining all threads, etc.) but this should be enough for testing.
Also,
(ql:quickload "serve")
This is not required in serve.lisp, the code by itself should not contain invocations to ql:quickload, it is sufficient to do it in the build command.
Likewise:
--eval '(load "serve.asd")' \
can be removed if you put your .asd file in a place where quicklisp can find it, see 4.1 Configuring ASDF to find your systems.

Error opening shared object: how do I embed shared object or specify a different pathname programmatically?

So, I am deploying lisp image to server for a webapp. It has been a nice experience so far, except for this particular case:
I am generating the webapp using this:
sbcl --dynamic-space-size 1024 \
--noinform \
--load $HOME/quicklisp/setup.lisp \
--eval '(ql:quickload :webapp)' \
--eval '(swank-loader:init :load-contribs t)' \
--eval "(sb-ext:save-lisp-and-die \"webapp\" :toplevel #'webapp::executable-entry-point :executable t :compression t)"
However, when I start the webapp on the server, I am thrown into the debugger:
debugger invoked on a SIMPLE-ERROR in thread
#<THREAD "main thread" RUNNING {10006285B3}>:
Error opening shared object "/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so":
/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so: cannot open shared object file: No such file or directory.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE ] Skip this shared object and continue.
1: [RETRY ] Retry loading this shared object.
2: [CHANGE-PATHNAME] Specify a different pathname to load the shared object from.
3: [ABORT ] Exit from the current thread.
(SB-SYS:DLOPEN-OR-LOSE #S(SB-ALIEN::SHARED-OBJECT :PATHNAME #P"/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so" :NAMESTRING "/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so" :HANDLE NIL :DONT-SAVE NIL))
0] back
Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {10006285B3}>
0: (SB-SYS:DLOPEN-OR-LOSE #S(SB-ALIEN::SHARED-OBJECT :PATHNAME #P"/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so" :NAMESTRING "/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so" :HANDLE NIL :DONT-SAVE NIL))
1: (SB-ALIEN::TRY-REOPEN-SHARED-OBJECT #S(SB-ALIEN::SHARED-OBJECT :PATHNAME #P"/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so" :NAMESTRING "/home/username/quicklisp/dists/quicklisp/software/clsql-20160208-git/db-mysql/clsql_mysql64.so" :HANDLE NIL :DONT-SAVE NIL))
2: (SB-SYS:REOPEN-SHARED-OBJECTS)
3: (SB-IMPL::FOREIGN-REINIT)
4: (SB-IMPL::REINIT)
5: ((FLET SB-UNIX::BODY :IN SAVE-LISP-AND-DIE))
6: ((FLET "WITHOUT-INTERRUPTS-BODY-36" :IN SAVE-LISP-AND-DIE))
7: ((LABELS SB-IMPL::RESTART-LISP :IN SAVE-LISP-AND-DIE))
0] 3
I'm not sure if this is limited to clsql - is there a way to embed the shared object or specify the pathname programmatically, since this looks like the error occurs even before the toplevel function is even called.
There does exist one two year old discussion about embedding shared object files into lisp images - the recommendation was to go with ECL, at least until ASDF instructions are clear.
For SBCL, I also discovered sb-alien:load-shared-object with the :dont-save nil option; but not sure how to put it to use here.
In the general case, deploy should do the trick. (Thanks to u/flaming_bird for pointing it out here.)
However, in this particular case, clsql looks for the .so files in clsql-sys:*foreign-library-search-paths*. This "lookup" happens during the compilation / make. Once done, the path that was used gets hardcoded into the image, and an error will be thrown if the library is not at that exact same path.
An alternative is to delete (or rename) the .so file from the clsql installation directory ($HOME/quicklisp/software/clsql-20160208-git/db-mysql/ in this case) before the image build and instead put it in the place (say, the current directory #P"./") where it is needed in the deployment server. And then, modifying the build command to push the shared-library location (eg. #P"./") does the trick:
sbcl --dynamic-space-size 1024 \
--noinform \
--load $HOME/quicklisp/setup.lisp \
--eval '(ql:quickload :clsql)' \
--eval '(push #P"./" clsql-sys:*foreign-library-search-paths*)' \
--eval '(ql:quickload :webapp)' \
--eval '(swank-loader:init :load-contribs t)' \
--eval '(py4cl2:pystop)' \
--eval "(sb-ext:save-lisp-and-die \"webapp\" :toplevel #'webapp::executable-entry-point :executable t :compression t)"

Read a line from a file

I'm trying to figure out how to read a line from a file with guile scheme.
When I ask it to "read port" or "read-char port", it successfully reads.
guile -c '(let ((port (open-input-file "foo.txt"))) (display (read port)) (newline) (close-port port))'
But, when I ask it to read-line, it fails.
guile -c '(let ((port (open-input-file "foo.txt"))) (display (read-line port)) (newline) (close-port port))'
Does anyone know what I am doing wrong? I am currently in the directory where foo.txt is located.
Your code fails with the message ERROR: Unbound variable: read-line, meaning that there is no definition for readline.
The function read-line has to be loaded using the form (use-modules (ice-9 rdelim)) before you can use it. (https://www.gnu.org/software/guile/manual/html_node/Input-and-Output.html)
This will then work:
guile -c '(use-modules (ice-9 rdelim)) (let ((port (open-input-file "foo.txt")))
(display (read-line port)) (newline) (close-port port))'

emacs tramp over an unreliable connection

I want to run R on a remote box under a local Emacs (I do not want to run Emacs on the remote box).
I can run R on a remote host using TRAMP:
(let ((default-directory "/user#remote:~"))
(R))
and everything works fine except that when the connection to remote is lost, R dies. This is no good because this means that I have to re-load all the data into R after restarting it, which takes time.
Is it possible to tell TRAMP to use a persistent terminal? (GNU Screen or tmux or Mosh or dtach)
See also emacs-devel thread tramp:sshx:(screen|tmux).
Here is an alternative approach using dtach:
(defvar R-remote-host "remote-server")
(defvar R-remote-session "R")
(defvar R-remote-directory "~")
(defun R-remote (&optional remote-host session directory)
"Connect to the remote-host's dtach session running R."
(interactive (list
(read-from-minibuffer "R remote host: " R-remote-host)
(read-from-minibuffer "R remote session: " R-remote-session)
(read-from-minibuffer "R remote directory: " R-remote-directory)))
(pop-to-buffer (make-comint (concat "remote-" session)
"ssh" nil "-t" "-t" remote-host
"cd" directory ";"
"dtach" "-A" (concat ".dtach-" session)
"-z" "-E" "-r" "none"
inferior-R-program-name "--no-readline"
inferior-R-args))
(ess-remote (process-name (get-buffer-process (current-buffer))) "R")
(setq comint-process-echoes t))
Call M-x R-remote RET RET RET RET.
It works for me.
PS. The answer to the problem (as opposed to the question as asked) is to use ein with Jupyter.
Here is how to use ESS with R running in a remote screen session:
ssh to the remote host (outside of emacs)
start screen session
detach it
open shell in emacs (M-x shell)
ssh to the remote host again in the emacs shell
resume the screen session (screen -r)
start R
finally attach ESS to the R process using M-x ess-remote in the shell buffer where you started R
There are more details, screenshots, and keybindings in this post http://blog.nguyenvq.com/2010/07/11/using-r-ess-remote-with-screen-in-emacs/

Open an Emacs buffer when a command tries to open an editor in shell-mode

I like to use Emacs' shell mode, but it has a few deficiencies. One of those is that it's not smart enough to open a new buffer when a shell command tries to invoke an editor. For example with the environment variable VISUAL set to vim I get the following from svn propedit:
$ svn propedit svn:externals .
"svn-prop.tmp" 2L, 149C[1;1H
~ [4;1H~ [5;1H~ [6;1H~ [7;1H~
...
(It may be hard to tell from the representation, but it's a horrible, ugly mess.)
With VISUAL set to "emacs -nw", I get
$ svn propedit svn:externals .
emacs: Terminal type "dumb" is not powerful enough to run Emacs.
It lacks the ability to position the cursor.
If that is not the actual type of terminal you have,
use the Bourne shell command `TERM=... export TERM' (C-shell:
`setenv TERM ...') to specify the correct type. It may be necessary
to do `unset TERMINFO' (C-shell: `unsetenv TERMINFO') as well.svn: system('emacs -nw svn-prop.tmp') returned 256
(It works with VISUAL set to just emacs, but only from inside an Emacs X window, not inside a terminal session.)
Is there a way to get shell mode to do the right thing here and open up a new buffer on behalf of the command line process?
You can attach to an Emacs session through emacsclient. First, start the emacs server with
M-x server-start
or add (server-start) to your .emacs. Then,
export VISUAL=emacsclient
Edit away.
Note:
The versions of emacs and emacsclient must agree. If you have multiple versions of Emacs installed, make sure you invoke the version of emacsclient corresponding to the version of Emacs running the server.
If you start the server in multiple Emacs processes/frames (e.g., because (server-start) is in your .emacs), the buffer will be created in the last frame to start the server.
There's emacsclient, gnuserv, and in Emacs 23, multi-tty that are all useful for this. Actually I think in Emacs 23, emacsclient has all of the interesting functionality of gnuserv.
Not entirely true. ansi-term can run an emacs fine (although I usually run mg for commit logs, in the rare event I don't commit from emacs directly). eshell can also run an emacs if you start a screen first and run it from within there.
Along with using emacs client/server, I am using this script to invoke emacs.
This will start emacs if it is not running yet, or just open a new emacs buffer in the running emacs (using gnuclient). It runs in the background by default, but can be run in the foreground for processes that expect some input. For example, I am using this as my source control editor, when entering a change list description. I have "SVN_EDITOR=emacs sync", so I can do "svn commit" in an emacs shell, and it will open the svn editor in a new emacs buffer in the same emacs. When I close the buffer, "svn commit" continues. Pretty useful.
#!/bin/sh
if [ -z $EMACS_CMD ]; then
EMACS_CMD="/usr/bin/emacs"
fi
if [ -z $GNUCLIENT_CMD ]; then
GNUCLIENT_CMD="/usr/bin/gnuclient"
fi
if [ "$1" = "sync" ]; then
shift 1
sync=true
else
sync=false
fi
cmd="${EMACS_CMD} $*"
lsof $EMACS_CMD | grep $USER >/dev/null 2>&1
if [ "$?" -ne "1" ]; then
cmd="${GNUCLIENT_CMD} $*"
fi
if [ $sync = "true" ]; then
$cmd
else
$cmd &
fi
I wanted to do something similar for merging in an emacs shell via mercurial. Thanks to the posters here, i found the way. two steps:
add: (start-server) in your .emacs file (remember to load-file after your change)
in your hgrc:
[merge-tools]
emacs.executable = emacsclient
emacs.premerge = False
emacs.args = --eval "(ediff-merge-with-ancestor \"$local\" \"$other\" \"$base\" nil \"$output\")"
When I have (start-server) in my .emacs I get this error....
Debugger entered--Lisp error: (void-function start-server)
(start-server)
eval-buffer(#<buffer *load*> nil "/Users/jarrold/.emacs" nil t) ; Reading at buffer position 22768
load-with-code-conversion("/Users/jarrold/.emacs" "/Users/jarrold/.emacs" t t)
load("~/.emacs" t t)
#[nil "^H\205\276^# \306=\203^Q^#\307^H\310Q\202A^# \311=\2033^#\312\307\313\314#\203#^#\315\202A^#\312\307\$
command-line()
normal-top-level()
....I am using GNU Emacs 22.1.1
And this is the version of Mac OS-X I am using:
shandemo 511 $ uname -a Darwin Facilitys-MacBook-Pro.local 10.8.0
Darwin Kernel Version 10.8.0: Tue Jun 7 16:33:36 PDT 2011;
root:xnu-1504.15.3~1/RELEASE_I386 i386
Note that m-x ansi-term appears to allow me to successfully hg commit inside of its shell. However, that shell does not let me scroll through the buffer with e.g. c-p or c-n so I would prefer to us m-x shell.

Resources