Hello list,
I want to accomplish the following: every time a lisp error happens, the whole backtrace and some additional information is sent to my mail account. The question is, what is the best way to do it with Hunchentoot? (I do know how to send email messages from lisp with the help of mel-base).
The second question is: how do you use CLSQL thread-safely with Hunchentoot / SBCL? SBCL don't provide thread properties, so my first solution[1] was to create a hash-table and to assign each thread a connection, to check from every thread whether it already has a connection, and then to clean that hash-table periodically, closing connections for dead threads. That worked pretty well, but I was afraid of exhausting limit of database threads somehow, so I switched to the second solution.
The second solution[2] is to open new connection every time I need to save something to database or read something from it, and to close it right after that. That of course solves connection limit problem (unless I have very many users simultaneously, which is not expected in the near term), however it's much slower.
Anybody can share his/her strategy?
Best regards, Victor.
[1] http://paste.lisp.org/display/39787 (defparameter *database-mutex* (sb-thread:make-mutex :name "database lock")) (defparameter *threads-connection* (make-hash-table))
(defun setup-connection () (connect '("localhost" "lj" "victor" "victor") :database-type :postgresql :if-exists :new))
(defun db () (handler-case (sb-thread:with-mutex (*database-mutex*) (or (gethash sb-thread:*current-thread* *threads-connection*) (setf (gethash sb-thread:*current-thread* *threads-connection*) (setup-connection)))) (error () (progn (close-old-connections) (db)))))
(defun close-old-connections () (maphash #'(lambda (thread connection) (unless (sb-thread:thread-alive-p thread) (clsql:disconnect :database connection) (remhash thread *threads-connection*))) *threads-connection*))
(let ((old-select (symbol-function 'clsql:select))) (defun select (&rest args) (let ((*default-database* (db))) (apply old-select args))))
(let ((old-insert-records (symbol-function 'insert-records))) (defun insert-records (&rest args) (apply old-insert-records :database (db) args)))
[2] http://paste.lisp.org/display/39787#1 (defun db () (connect *connection-spec* :database-type :postgresql :if-exists :new))
(let ((old-select (symbol-function 'clsql:select))) (defun select (&rest args) (let ((clsql:*default-database* (db))) (prog1 (apply old-select args) (disconnect :database clsql:*default-database*)))))
(let ((old-insert-records (symbol-function 'insert-records))) (defun insert-records (&rest args) (let ((db (db))) (prog1 (apply old-insert-records :database db args) (disconnect :database db)))))
For error handling just supply your own function for *http-error-handler*.
From there you can send an email as well or/and show a message to the user.
Something like this:
(setq *http-error-handler* 'my-error-handler)
(defun my-error-handler (return-code) (if (= +http-moved-temporarily+ return-code) (return-from my-error-handler)) (let ((backtrace (get-backtrace 0)) (session (start-session))) ;; retrieve stuff out of your session (send-email "Critical error" (format nil "RetCode: ~a, Backtrace: ~a" return-code backtrace)) ;; My email function (with-html (:html (:head (:title "Error Page") (:link :rel "stylesheet" :type "text/css" :href *design-css*) (:script :language "JavaScript" :src *js-main-script* :type "text/javascript" "")) (:body (:h2 "Sorry, a server-side error has occured.")))
Note that you need to make the size of the html code (css, javascript + html) of the error page more than 512Bytes so that MS IE 7.0 would show your information instead of its own message.
Andrew
On 4/15/07, Victor Kryukov victor.kryukov@gmail.com wrote:
Hello list,
I want to accomplish the following: every time a lisp error happens, the whole backtrace and some additional information is sent to my mail account. The question is, what is the best way to do it with Hunchentoot? (I do know how to send email messages from lisp with the help of mel-base).
The second question is: how do you use CLSQL thread-safely with Hunchentoot / SBCL? SBCL don't provide thread properties, so my first solution[1] was to create a hash-table and to assign each thread a connection, to check from every thread whether it already has a connection, and then to clean that hash-table periodically, closing connections for dead threads. That worked pretty well, but I was afraid of exhausting limit of database threads somehow, so I switched to the second solution.
The second solution[2] is to open new connection every time I need to save something to database or read something from it, and to close it right after that. That of course solves connection limit problem (unless I have very many users simultaneously, which is not expected in the near term), however it's much slower.
Anybody can share his/her strategy?
Best regards, Victor.
[1] http://paste.lisp.org/display/39787 (defparameter *database-mutex* (sb-thread:make-mutex :name "database lock")) (defparameter *threads-connection* (make-hash-table))
(defun setup-connection () (connect '("localhost" "lj" "victor" "victor") :database-type :postgresql :if-exists :new))
(defun db () (handler-case (sb-thread:with-mutex (*database-mutex*) (or (gethash sb-thread:*current-thread* *threads-connection*) (setf (gethash sb-thread:*current-thread* *threads-connection*) (setup-connection)))) (error () (progn (close-old-connections) (db)))))
(defun close-old-connections () (maphash #'(lambda (thread connection) (unless (sb-thread:thread-alive-p thread) (clsql:disconnect :database connection) (remhash thread *threads-connection*))) *threads-connection*))
(let ((old-select (symbol-function 'clsql:select))) (defun select (&rest args) (let ((*default-database* (db))) (apply old-select args))))
(let ((old-insert-records (symbol-function 'insert-records))) (defun insert-records (&rest args) (apply old-insert-records :database (db) args)))
[2] http://paste.lisp.org/display/39787#1 (defun db () (connect *connection-spec* :database-type :postgresql :if-exists :new))
(let ((old-select (symbol-function 'clsql:select))) (defun select (&rest args) (let ((clsql:*default-database* (db))) (prog1 (apply old-select args) (disconnect :database clsql:*default-database*)))))
(let ((old-insert-records (symbol-function 'insert-records))) (defun insert-records (&rest args) (let ((db (db))) (prog1 (apply old-insert-records :database db args) (disconnect :database db)))))
-- Yours Sincerely, Victor Kryukov _______________________________________________ tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
On Sun, 15 Apr 2007 21:21:02 -0400, "Andrei Stebakov" lispercat@gmail.com wrote:
(let ((backtrace (get-backtrace 0))
This is not portable across all platforms supported by Hunchentoot, the argument to GET-BACKTRACE should be a condition.
Besides, for catching errors and emailing them I'd rather use an approach similar to what I described here:
http://common-lisp.net/pipermail/tbnl-devel/2007-April/001153.html
Andrei and Edi - thanks a lot for you suggestions/comments, I'll investigate this options.
On 4/16/07, Edi Weitz edi@agharta.de wrote:
On Sun, 15 Apr 2007 21:21:02 -0400, "Andrei Stebakov" lispercat@gmail.com wrote:
(let ((backtrace (get-backtrace 0))
This is not portable across all platforms supported by Hunchentoot, the argument to GET-BACKTRACE should be a condition.
Besides, for catching errors and emailing them I'd rather use an approach similar to what I described here:
http://common-lisp.net/pipermail/tbnl-devel/2007-April/001153.html _______________________________________________ tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
On Sun, 15 Apr 2007 19:03:15 -0500, "Victor Kryukov" victor.kryukov@gmail.com wrote:
The second question is: how do you use CLSQL thread-safely with Hunchentoot / SBCL? SBCL don't provide thread properties, so my first solution[1] was to create a hash-table and to assign each thread a connection, to check from every thread whether it already has a connection, and then to clean that hash-table periodically, closing connections for dead threads. That worked pretty well, but I was afraid of exhausting limit of database threads somehow, so I switched to the second solution.
The second solution[2] is to open new connection every time I need to save something to database or read something from it, and to close it right after that. That of course solves connection limit problem (unless I have very many users simultaneously, which is not expected in the near term), however it's much slower.
Anybody can share his/her strategy?
I'd use [2] but with pooled connections. See the :POOL keyword argument to CONNECT in CLSQL. That should get rid of your performance problems.
(let ((old-select (symbol-function 'clsql:select)))
That technique will break if you recompile the file the code is in. I'd propose to do this (shadow the original SELECT with your own function) with packages.
Thanks Edi - using packages is indeed a better idea that just shadowing SELECT right in place.
I'll try to experiment with :pool again, though I remember having some troubles using it first time I tried.
BTW, it may be a good idea to have some place where people could contribute small snippets/examples of code related to your libraries rather then post them everywhere in their blogs, mailing lists etc. I myself would be happy to contribute finalized versions of thread-safe CLSQL usage / redefining error handlers; though this examples may sound obvious for more experienced Lisp programmers, they would probaly save some time for less experienced one, like myself. E.g. I could setup a separate page on cliki.net. Do you think that makes any sence?
Best Regards, Victor.
On 4/16/07, Edi Weitz edi@agharta.de wrote:
On Sun, 15 Apr 2007 19:03:15 -0500, "Victor Kryukov" victor.kryukov@gmail.com wrote:
The second question is: how do you use CLSQL thread-safely with Hunchentoot / SBCL? SBCL don't provide thread properties, so my first solution[1] was to create a hash-table and to assign each thread a connection, to check from every thread whether it already has a connection, and then to clean that hash-table periodically, closing connections for dead threads. That worked pretty well, but I was afraid of exhausting limit of database threads somehow, so I switched to the second solution.
The second solution[2] is to open new connection every time I need to save something to database or read something from it, and to close it right after that. That of course solves connection limit problem (unless I have very many users simultaneously, which is not expected in the near term), however it's much slower.
Anybody can share his/her strategy?
I'd use [2] but with pooled connections. See the :POOL keyword argument to CONNECT in CLSQL. That should get rid of your performance problems.
(let ((old-select (symbol-function 'clsql:select)))
That technique will break if you recompile the file the code is in. I'd propose to do this (shadow the original SELECT with your own function) with packages. _______________________________________________ tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
On Mon, 16 Apr 2007 12:51:12 -0500, "Victor Kryukov" victor.kryukov@gmail.com wrote:
BTW, it may be a good idea to have some place where people could contribute small snippets/examples of code related to your libraries rather then post them everywhere in their blogs, mailing lists etc. I myself would be happy to contribute finalized versions of thread-safe CLSQL usage / redefining error handlers; though this examples may sound obvious for more experienced Lisp programmers, they would probaly save some time for less experienced one, like myself. E.g. I could setup a separate page on cliki.net. Do you think that makes any sence?
I'm fine with everything that doesn't eat up more of my time... :)
Seriously, if you think you can contribute something worthwhile, it's OK with me if you want to use the common-lisp.net resources allocated to Hunchentoot (TBNL) for it (if that helps). You might want to coordinate this with Mac Chan who had similar plans. CLiki or other locations are also fine, of course, this one for example:
"Victor Kryukov" victor.kryukov@gmail.com writes:
The second question is: how do you use CLSQL thread-safely with Hunchentoot / SBCL? SBCL don't provide thread properties, so my first solution[1] was to create a hash-table and to assign each thread a connection, to check from every thread whether it already has a connection, and then to clean that hash-table periodically, closing connections for dead threads. That worked pretty well, but I was afraid of exhausting limit of database threads somehow, so I switched to the second solution.
i put any database-communication out of an function, called via the *dispatch-table*, into a (with-db (bla bla)) macro:
(defmacro with-db (&body body) `(let ((clsql:*default-database* (clsql:connect (list "127.0.0.1" "bla" "bla" "bla") :pool t :database-type :postgresql))) (unwind-protect (progn ,@body) (clsql:disconnect :database clsql:*default-database*))))
so any thread should get his own *default-database* from the clsql connection pool?
On Mon, 16 Apr, 2007 at 22:12:31 +0200, Otto Diesenbacher wrote:
i put any database-communication out of an function, called via the *dispatch-table*, into a (with-db (bla bla)) macro:
CLSQL already has that kind of macros: http://clsql.b9.com/manual/with-database.html http://clsql.b9.com/manual/with-default-database.html
-- Registered Linux User #124759
Scribit Victor Kryukov dies 15/04/2007 hora 19:03:
SBCL don't provide thread properties, so my first solution[1] was to create a hash-table and to assign each thread a connection, to check from every thread whether it already has a connection, and then to clean that hash-table periodically, closing connections for dead threads.
Now that I'm used to Lisp's first-class everything, I'd rather mimic it everywhere. For example, you could create a custom thread object whose creation not only starts a thread but takes care of creating a DB connection (or taking it out of a pool) and making it accessible in the thread, probably with a dynamic variable...
Quickly, Pierre