Shadowing special variables in threaded handlers

Stelian Ionescu sionescu at cddr.org
Fri Dec 31 08:51:17 UTC 2021


On Fri, 2021-12-31 at 15:48 +1100, Duncan Bayne wrote:
> Chun Tian writes:
> 
> > The difference here is that now (lambda (stream) ...) is a closure
> > which will contain a local version of *foo* at the time when
> > (create-server) is called.  This kind of uses of lambda functions
> > is
> > like a cheap object with a member variable.
> 
> Thanks for the suggestion - yes, this does work as expected, but
> introduces a difficulty with the API of the library.
> 
> The germinal code is as follows (edited to remove large swathes and
> just
> focus on the relevant bits):
> 
> =====
> (defvar *germinal-cert* "/etc/germinal/cert.pem")
> (defvar *germinal-cert-key* "/etc/germinal/key.pem")
> (defvar *germinal-tls-context* nil "Variable used to store global TLS
> context")
> 
> ;; snip
> 
>   (with-global-context (*germinal-tls-context* :auto-free-p (not
> background))
>      (usocket:socket-server host port #'gemini-handler ()
>                            :multi-threading t
>                            :element-type '(unsigned-byte 8)
>                            :in-new-thread background)))
> 
> ;; snip
> 
> (defun gemini-handler (stream)
>   "The main Gemini request handler. Sets up TLS and sets up request
> and response"
>   (handler-case
>       (let* ((tls-stream (make-ssl-server-stream stream
>                                                  :certificate
> *germinal-cert*
>                                                  :key *germinal-cert-
> key*))
> ;; snip
> =====
> 
> So replacing the handler function with a lambda that creates a
> closure
> works ... but breaks the non-testing case where you just want to setq
> the special variables in your app startup and be done with it.
> 
> The best approach I can think of is something like ...
> 
> =====
> ;; snip
>   (with-global-context (*germinal-tls-context* :auto-free-p (not
> background))
>      (usocket:socket-server host port
>                            (let ((*threaded-cert* *germinal-cert*)
>                                  (*threaded-cert-key* *germinal-cert-
> key*))
>                                    (lambda (stream) (gemini-handler
> stream)))
> ;; snip
>       (let* ((tls-stream (make-ssl-server-stream stream
>                                                  :certificate
> *threaded-cert*
>                                                  :key *threaded-cert-
> key*))
> ;; snip
> =====
> 
> Which seems weird, but also gives the best of both worlds; the
> ability
> to shadow variables for testing purposes, but also setq the *same*
> variables for global configuration.
> 
> Thoughts / opinions?

You're overusing special variables. You should also make up your mind 
on the concurrency model and try to avoid allowing both foreground and
background operations.


(defun gemini-handler (stream cert key)
  "The main Gemini request handler. Sets up TLS and sets up request and response"
  (handler-case
      (let* ((tls-stream (make-ssl-server-stream stream
                                                 :certificate cert
                                                 :key key))))))

(defun make-gemini-handler (cert key)
  (lambda (stream) (gemini-handler stream cert key)))

(with-global-context (*germinal-tls-context* :auto-free-p (not background))
  (usocket:socket-server host port (make-gemini-handler *germinal-cert* *germinal-cert-key*)))

-- 
Stelian Ionescu

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: This is a digitally signed message part
URL: <https://mailman.common-lisp.net/pipermail/usocket-devel/attachments/20211231/43827be5/attachment.sig>


More information about the usocket-devel mailing list