Hi, Imagine the following simple "algorithm" (or strategy): "shadowed values always have priority, otherwise the current value of global variables are used." Problem is that, how could the lambda function know it was called with a shadowed value? An easy way is to use another global variable which should be never changed by SETQ:
So consider the following modified examples: (defvar *foo* "original value") (defvar *shadowed* nil) (defun create-server (&optional (port 1965)) (usocket:socket-server "0.0.0.0" port (let ((foo *foo*) (shadowed *shadowed*)) (lambda (stream) (let ((v (if shadowed foo *foo*))) (write v :stream stream)))) () :multi-threading t :element-type 'character :in-new-thread t)) (defparameter *server1* (let ((*foo* "shadowed value") (*shadowed* t)) (create-server 1965))) (defparameter *server2* (create-server 1966)) There are two "servers" listening on port 1965 and 1966. Now I have: $ telnet 127.0.0.1 1965 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. "shadowed value" $ telnet 127.0.0.1 1966 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. "original value" After I changed the value of *foo* by (SETQ *FOO* "new value"): $ telnet 127.0.0.1 1965 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. "shadowed value" $ telnet 127.0.0.1 1966 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. "new value" But note that, in general, if you change the value of a global variable from one thread, the change may not be immediately visible from another thread, unless you use something like locks (or atomic updates) from multi-threading libraries. The related issue is not in scope of the socket library. Regards, Chun Tian > On Dec 31, 2021, at 05:48, Duncan Bayne <dun...@bayne.id.au> 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? > > -- > 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.
signature.asc
Description: Message signed with OpenPGP