[Sbcl-devel] thread local specials

25 views
Skip to first unread message

Elijah Stone

unread,
Sep 28, 2023, 8:24:33 PM9/28/23
to sbcl-...@lists.sourceforge.net
I want to add thread local specials to sbcl. I seem to recall there was a
long-standing ticket on the issue, and I have an application that needs them.

I see there is some cursory support for them already, but it seems intended
only for internal use, and is fused off from use by users. In particular,
this seems related to the fact that %define-thread-local does its own
special-case handling for them. Is there a good reason for this? Why not
treat them more generically, like other specials?

-E



_______________________________________________
Sbcl-devel mailing list
Sbcl-...@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sbcl-devel

Stas Boukarev

unread,
Sep 28, 2023, 8:44:20 PM9/28/23
to Elijah Stone, sbcl-...@lists.sourceforge.net
What are thread local specials and what do they do?

Douglas Katzman via Sbcl-devel

unread,
Sep 28, 2023, 8:49:32 PM9/28/23
to Stas Boukarev, sbcl-...@lists.sourceforge.net
On Thu, Sep 28, 2023 at 8:44 PM Stas Boukarev <stas...@gmail.com> wrote:
What are thread local specials and what do they do?

It's supposed to ensure that even if you didn't specially bind in a thread, that you can never set the top-level value ("in" the symbol) for certain symbols.
I searched the mailing list for some work that Gabor Melis and others did on this and found stuff as far back as 2006
I don't know why there has never been enough of a push to land successfully in the tree.

I will speculate that many users rely on a thin layer provided by bordeaux-threads, or their own design. All it really takes is a PROGV on a list of things to be enforced special; and I think good design would mandate that all specials are known on thread start - you can't randomly make more. However, that's the case the preceding patches want to address: deeming that some symbol no matter where set in any running thread can not touch the global value. 

I think it can be done with only overhead in the slow path of SET, i.e. we don't need to slow down dynbind or reading the value. Maybe?   It seems weird. QPX has never needed that, which is typically my yardstick of what I need.

Elijah Stone

unread,
Sep 28, 2023, 8:58:28 PM9/28/23
to Stas Boukarev, sbcl-...@lists.sourceforge.net
A thread local special is a special which always has a thread local binding.
This improves the performance of accessing it, because it's not necessary to
check if it already has a global binding. And it has a much more reasonable
semantics, because there's never a question about whether a binding might be
exclusive to a thread or shared between threads.

The semantics I want are slightly different from the ones used by sbcl's
internal define-thread-local currently. (But they are similar to
functionality supported by bordeaux threads.) Defining a thread-local special
should allow you to supply up to _two_ forms: one which is evaluated right
away and gives the initial value on threads that already exist, and one which
is evaluated whenever a thread is created, to give the initial binding for
that newly created thread. The latter is evaluated by the thread doing the
creating, not the newly created thread. Unlike sbcl, which has only one form,
and always evaluates it in the newly created thread.

The conceptual reason for this (which matches my use case) is that there
should be an ordering between the creation of a thread and all subsequent
actions taken by that thread. For CPU reordering, this is trivial--creating a
thread will certainly involve the execution of some serialising
instructions--but for coherency protocols implemented in software, an explicit
handshake may be needed, which may rely on dynamic state in the creating
thread. (It might also be desirable to have a different mechanism for
specifying some actions to be performed by the created thread upon
initialisation--like a thread constructor.)

-E

Elijah Stone

unread,
Sep 28, 2023, 9:05:43 PM9/28/23
to Douglas Katzman, sbcl-...@lists.sourceforge.net
On Thu, 28 Sep 2023, Douglas Katzman wrote:

> I think good design would mandate that all specials are known on thread
> start - you can't randomly make more

That seems antithetical to interactive development--and we can make new
specials at runtime anyway; what is the problem?

> I don't know why there has never been enough of a push to land
> successfully in the tree.

I plan to do it--I just want to understand if there is a good reason for the
current design of the internal thread-locals (which it seems was last changed
by you in 2019).

-E

Stas Boukarev

unread,
Sep 28, 2023, 9:08:44 PM9/28/23
to Elijah Stone, sbcl-...@lists.sourceforge.net
A complicated interface just for some micro-optimizations that nobody will use. Not a good proposition.

Elijah Stone

unread,
Sep 28, 2023, 9:12:07 PM9/28/23
to Stas Boukarev, sbcl-...@lists.sourceforge.net
It is not just for a micro-optimisation; I cited both performance and
semantics. And I am not the only person who wants this; see Doug's links.
Imo, the behaviour is much simpler than the way specials currently work.

Douglas Katzman via Sbcl-devel

unread,
Sep 28, 2023, 9:20:11 PM9/28/23
to Elijah Stone, sbcl-...@lists.sourceforge.net

That seems antithetical to interactive development--and we can make new
specials at runtime anyway; what is the problem?

Interactive development may be how quality code is developed but not how quality code is productionized.  I would be opposed to anything that degrades runtime performance, but if it can be done cleanly and with no degradation then I'm not opposed.  I suspect (but can not assert that) my criteria are not met by the patch proposals

Dmitry Ignatiev

unread,
Sep 29, 2023, 8:05:50 AM9/29/23
to Stas Boukarev, sbcl-...@lists.sourceforge.net
Although thread-local-only variables could be replaced with a synchronized weak hash table that maps threads to some data, there could be several different use cases where such optimizations would be very useful, if not critical.

Consider custom synchronization primitives. E.g. a reader/writer lock.
For example, the .NET implementation stores a lock state associated with a thread inside a thread-local list:

https://github.com/microsoft/referencesource/blob/51cf7850defa8a17d815b4700b67116e3fa283c2/System.Core/System/threading/ReaderWriterLockSlim/ReaderWriterLockSlim.cs

A common special variable is not enough for this in cases where you are unable to ensure 'top-level' thread binding.
I can think of at least two such cases:

1) Foreign thread callbacks
2) Some sort of a 'main' thread (e.g. SLIME repl thread)

Besides, even if you are using bordeaux-threads compatibility layer which allows you to define default special bindings for new threads, you must ensure that all 3rd party code also uses bt library for thread creation. Which is kind of difficult(again, consider SLIME/Swank).

So, you have to revert to the aforementioned synchronized weak hash table. But introducing such a data structure inside a synchronization primitive is a sure way to shoot yourself in the foot.

пт, 29 сент. 2023 г. в 04:09, Stas Boukarev <stas...@gmail.com>:

Gábor Melis

unread,
Sep 29, 2023, 8:57:28 AM9/29/23
to Douglas Katzman, sbcl-...@lists.sourceforge.net
On Fri, 29 Sept 2023 at 01:49, Douglas Katzman via Sbcl-devel
<sbcl-...@lists.sourceforge.net> wrote:
> I searched the mailing list for some work that Gabor Melis and others did on this and found stuff as far back as 2006
> (https://groups.google.com/g/sbcl-devel/c/l3qTR8oPcrw/m/FE2DYm_ciYwJ) but more recently "only" 2015 (https://groups.google.com/g/sbcl-devel/c/SyUGDJ50Yqg/m/oLssTGbjCgAJ)
> I don't know why there has never been enough of a push to land successfully in the tree.

Because we could find no solution with clean semantics. It's racy. I
worried about what happens when the global binding of a symbol is set
in a thread created before the it is declared thread local (or added
to some auto-bind list). My safest attempt looped over existing
threads and injected binding with INTERRUPT-THREAD ...

Since there is no way to make a special not special,
define-thread-local-var could require that it's not yet special, and
that could _mostly_ work. Unfortunately, even a non-special symbol's
value cell can be accessed.

Elijah Stone

unread,
Sep 30, 2023, 2:40:33 AM9/30/23
to Gábor Melis, sbcl-...@lists.sourceforge.net
On Fri, 29 Sep 2023, Gábor Melis wrote:

> Because we could find no solution with clean semantics

It's a fair point, but I think also (unfortunately) somewhat unavoidable and
also precedented, since sbcl is not a jit compiler. Consider trying to change
a constant into a special (or for that matter just redefining it), or
redefining a struct...

A best-effort approach seems acceptable.

-E

Gábor Melis

unread,
Sep 30, 2023, 6:46:16 PM9/30/23
to Elijah Stone, sbcl-...@lists.sourceforge.net

Maybe. How do you propose to deal with existing threads?

Elijah Stone

unread,
Sep 30, 2023, 8:50:27 PM9/30/23
to Gábor Melis, sbcl-...@lists.sourceforge.net
On Sat, 30 Sep 2023, Gábor Melis wrote:

> Maybe. How do you propose to deal with existing threads?

Hmmm. How about this:

1. Take the free-tls-index lock

2. Check the symbol's tls-index. If it's still unset (likely) then grab a new
tls-index for it, install the initial value in all extant threads' tls blocks,
and then release the lock; done

3. Otherwise, other threads may have active bindings. So do the equivalent of
MAKUNBOUND on the global binding, but with some side-band information to
communicate the nature of the variable and its initial value. Threads can
check this side band on the slow path for UNBOUND-SYMBOL-ERROR, and then
recover (only happens once per thread per variable).

This allows a variable to transition from being special to being thread-local.
I cannot think of a way to allow a variable to transition the other way
without harming performance or fundamentally changing the design of sbcl.
(There is the same problem with transitioning a variable from global to
special e.g., and there are already global variables).

One annoying question is whether this leaks the value of the initial binding
(once all threads have acknowledged it and assuming they all reassign it to
something else afterwards). But I guess that, when the gc runs, it should be
able to clean up anything that's still in limbo, since all threads are
stopped, so there should be no problem there.
Reply all
Reply to author
Forward
0 new messages