Shadowing special variables in threaded handlers
Chun Tian
binghe.lisp at gmail.com
Fri Dec 31 09:09:08 UTC 2021
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 <duncan at 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.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: Message signed with OpenPGP
URL: <https://mailman.common-lisp.net/pipermail/usocket-devel/attachments/20211231/58ddf432/attachment-0001.sig>
More information about the usocket-devel
mailing list