Shadowing special variables in threaded handlers

Duncan Bayne duncan at bayne.id.au
Fri Dec 31 04:48:55 UTC 2021


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?

--
Duncan Bayne
+61 420 817 082 | https://duncan.bayne.id.au/

I usually check my mail every 24 - 48 hours.  If there's something
urgent going on, please send me an SMS or call me.



More information about the usocket-devel mailing list