ic

Car crashes with another outside pub during police chase in Sydney’s southwest - 7NEWS

  1. Car crashes with another outside pub during police chase in Sydney’s southwest  7NEWS
  2. ‘Flying past me’: Two hurt as car smashes into fence after police chase  Daily Telegraph
  3. Critical incident investigation into crash following police pursuit  Sydney Morning Herald
  4. Violent carjacking linked to Sydney crash that split car in two, police say  9News
  5. Update on crash after police chase  news.com.au




ic

Legitimacy of two Victorian local government elections in question after duplicate votes detected - ABC News

  1. Legitimacy of two Victorian local government elections in question after duplicate votes detected  ABC News
  2. Victorian council election results 2024 LIVE updates: Suspected postal vote tampering in council elections  Sydney Morning Herald
  3. VEC investigates potential vote tampering in two Melbourne councils  The Age




ic

Fundraising by Phone is for every Non-Profit: The Basics of Telephone fundraising.


 An increasing number of charities and nonprofit groups are electing to solicit donations through Telefundraising campaigns. Despite having different goals and needs, disparate organizations find that Telefundraising is an effective use of resources . For many groups, the option of telefundraisng makes sense.
For large groups with vast amounts of members, telephone fundraising provides an effective means of outreach.  Charities withsignificant fundraising budgets see impressive returns from telephone fundraising efforts. Large organizations are results oriented, this is why they continue to support Telefundraising campaigns.

Small non-profits use telephone fundraising efforts as ameans to gain support for little upfront investment.While wide-scale print and media campaigns are often beyond the reach of small organizations, phonecampaigns can be executed in an extremely cost effective manner. Adjustments to telephone campaigns are virtually instantaneous, while other types of fundraising efforts need much more planning to alter.
Organizations of any size can quickly realize significant benefits from telephone campaigns. The relative low cost of phone fundraising campaigns is an attractive feature, regardless of organizational size. Paired with simple initial equipment requirements, this makes raising money by phone an easy choice for most non-profits.


Raising money is not the only goal of telephone solicitation campaigns. Contacts that end without a donation can still prove to be useful. Basing success on donations alone, overlooks other possible benefits to an organization.
Receiving a call from an organization helps to personalize the charity to its donors. Phone calls keep organizations on the minds of their supporters. These calls provide donors with the latest information on the cause they support. Well informed donors provide more frequent support.
Donation calls properly made, can be an important source of feedback for an organization as well. Donors use telefundraisng contacts as an opportunity to sound-off about their feelings about an organization.  Organizations can take these valuable insights, and use them to tweak their efforts.
Solicitation calls can provide much more than donations alone. Fully grasping this truth greatly improves campaign results.Positive campaign results are limited only by the imaginations of the organizations which run them.


Calling campaigns are always an effective solicitation method. For the best possible outcomes, additional factors should be considered as well. Holidays represent an excellent time for telephone fundraising efforts. This time of year is when many donors are most receptive to giving.Wise non-profits leverage the holidays to increase their rate of success.
Topical campaigns have increase significance to givers. These campaigns can be combined with additional media efforts. By closely monitoring the news cycle, it is possible to create campaigns that take advantage of current news, and require no additional effort to spark media interest.
Close attention to relevant news is useful, but not essential to telephone fundraising. Calling campaigns that are well structured can drive their own press coverage.  Press coverage is useful in some cases, but not an absolute requirement for all calling campaigns.


Implementing a successful telephone campaign is not substantially different than other types of solicitation methods. Good Telefundraising applys traditional fundraising methods to the phone. The benefits of applying telephone fundraising are obvious.
Telephone campaigns are an excellent way to overcome the problem of donor fatigue. Combined with other strategies, Telefundraising can strengthen listing campaigns. Fundraising by phone can produce results in and of itself.
Carried out properly, there is no application where telephone fundraising campaigns cannot be attempted with positive outcome.Effective fundraising campaigns require detailed planning. Phone fundraising and other solicitation efforts should be designed to work in harmony.
Fundraising by telephone requires serious preparation. Calling efforts should be well managed and adequately staffed. The results of telephone campaigns are based largely on the dedication of the organizations which run them.


Pound for pound, telephone fundraising delivers better results than other fundraising techniques. The advantages of telephone fundraising fluctuate across organizations, but remain significant. While income is an important factor, it is not the only way to evaluate the performance of a fundraising effort.
Phone campaigns can be implemented with minimal staff requirements. Well managed small teams can out performs much larger groups . Small teams are capable of delivering large gains.
Changes to phone fundraising campaigns can be implemented without halting the camapaign. Important updates can be quickly integrated. News and current events can be incorporated into the campaigns virtually instantly.Comparable fundraising methods need significant time to alter.
Speaking directly with prospects ensures valid contact. Direct  contact with donors is assured with calling campaigns.




ic

Quick Rapport Technique.

As most fundraisers know, building rapport with donors keeps them on the phone and listening to your request. Rapport isn't a clever trick; it is a means of showing donors that you are interested in them and that you have at least some small things in common. We build rapport in almost every conversation we have whether on or off of the phone. Building rapport with someone you've just called for the first time can be a little harder, but it is certainly possible.

One simple technique for rapport building is knowing the state nicknames or motto's of the states that you're dialing into. Asking a donor how things are going in the Equality State rather than simply how things are going, makes your question more intimate. Donors really do open up when you take a personal interest in them. Listed below are the state nicknames.

StateNickname(s)
 Alabama
(No official nickname)[1]
 Alaska
 Arizona
 Arkansas
 California
  • El Dorado State
  • The Golden State[12][13] (previously used on license plates)
  • The Land of Sunshine and Opportunity
  • Golden West
  • Grape State
  • Land of Milk and Honey
  • Land of Fruits and Nuts
  • Where Stars Are Buried
  • The Cereal Bowl of the Nation
  • The Eureka State [14]
  • The Bear State (or Republic)
  • The Sunshine State (in disuse) (c.f. FL)
 Colorado
 Connecticut
  • Constitution State
  • Nutmeg State
  • Charter Oak State
 Delaware
 District of Columbia
 Florida
  • Alligator State[25]
  • Citrus State[citation needed]
  • Everglade State[25]
  • Flower State[25]
  • God's Waiting Room
  • Gulf State[25]
  • Hurricane State[citation needed]
  • La Florida
  • Manatee State[citation needed]
  • Orange State[25]
  • Peninsula State or Peninsular State[23]
  • Sunshine State (currently used on license plates)
  • Tropical State[Full Article

    ic

    What does it mean to "wane philosophical"?

    "To what extent is science a strong-link problem?", Sauropod Vertebra Picture of the Week, 10/30/2024 [emphasis added]: Here’s a fascinating and worrying news story in Science: a top US researcher apparently falsified a lot of images (at least) in papers that helped get experimental drugs on the market — papers that were published in top […]



    • Words words words

    ic

    A bushel of buzzwords from Japan; the advent of phoneticization

    Below are two lists of nominations for Japanese buzzword of the year.  Each has 30 entries, and from each list one will be chosen as the respective winner.  Since the two lists are already quite long and rich, I will keep my own comments (mostly at the bottom and focusing on phoneticization) to a minimum. […]




    ic

    Whimsical surnames, part 2 (again mostly German)

    [This is a guest post by Michael Witzel] A few months ago you published a discussion of whimsical surnames. Since then I have paid attention and have found new ones in  almost every news broadcast. It is said that there are 1 million (!) surnames in the German speaking area of some 95 million people […]




    ic

    Biblical and Budai Taiwanese: vernacular, literary; oral, written

    [This is a guest post by Denis Mair]      Cai Xutie was a Taiwanese woman who ran a family farm with her husband in a village near Jiayi in central Taiwan. She was a rice farmer and had never attended a public school. After her husband died in middle age, she sold some of the land, […]




    ic

    Cognition, culture, … and communication?

    An interesting recent review article (Wooster et al., "Animal cognition and culture mediate predator–prey interactions", Trends in Ecology & Evolution 2024) argues for bridging the academic silos of "predator-prey ecology" and "animal cognition and culture": Abstract: Predator–prey ecology and the study of animal cognition and culture have emerged as independent disciplines. Research combining these disciplines […]




    ic

    Geometriphylogenetics

    Today's xkcd: Mouseover title: "There's a maximum likelihood that I'm doing phylogenetics wrong." It's not that Randall is "doing phylogenetics wrong", but rather than he's applying it to an inappropriate problem. The OED's etymology for phylogeny is < German Phylogenie (E. Haeckel Gen. Morphol. der Organismen (1866) I. iii. 57) < Phylum phylum n. + […]



    • Linguistics in the comics

    ic

    Of all homonymic months, August is the most majestic

    I’m traveling for the long weekend. Either I’m having bad luck with the epic heat waves or there have been a lot of epic heat waves, because again the short road trip threatens to be tyrannized by the hot air. It did at least touch 100°F this time, so at least it is a proper respectable heat wave. We are in a place called Hocking Hills, whose AirBnB has these OBX-style stickers that say “HHO”, which could either be confusingly “Hills, HOcking,” or perhaps “Hocking Hills, Ohio”, but not “Hocking hills OHio” as one might expect. I plan to stick the sticker upside-down for “OHH”, as in “Ohh yeah, I need to write a post on Tom 7 Radar for the month of August, and I need to do it on this mediocre wi-fi which Google Internet Speed Test describes as ‘fine’ while everyone else drinks beers outside.” Fair enough: This is a self-imposed curse and one that’s easily tended to at any time during the month.

    During the month: I worked again on making my own video codec, which is a very bad way to spend one’s time, but I don’t think there are any modern lossless codecs that would be suitable for my use case. And I do like a data compression project because of the inherent benchmarkability. The use case is for the increasingly common situation where I have a program generating a series of video frames (e.g. BoVeX is making an animation), which I usually do by writing a sequence of PNG files to disk. I’m way ahead of PNG files so far even without doing any inter-frame stuff, which is not impressive, but does make me feel like it’s at least not totally pointless. (Still, it’s quite pointless: Sure I can make these files smaller at significant cost of complexity and encoding times, but these animations typically use space similar to like one second of 4K 60fps XF-AVC footage.)

    Sometimes programming your own lossless video codec is a bit too fast-paced so you need to write a Wikipedia article from scratch about Clairton Coke Works by digging through newspaper archives. I haven't even gotten to the last 30 years of its history yet! I also rounded out the Cyrillic in FixederSys though I don't think I've uploaded a new version of that yet. As usual I did some hacking on secret projects.

    UHH, elsewise, I did finish off Animal Well which I liked very much. My spoilerless advice to you is: Don't try to 100% this game without at least looking at a spoiler-controlled guide! But I did have fun once I felt like I was stuck-ish finishing the remaining postgame puzzles. I have also been playing Chippy, a bullet-hell twin-stick shooter that is quite hard (I usually feel good at this genre) and has several new good ideas in it. It's essentially all boss fights, and the chief innovation is that you fight the giant bosses by disconnecting pieces of them. I'm on the last boss so I will probably finish that one soon. As I have confessed many times, I like dumb first-person shooter games, and I played through Trepang 2 this month as well. It does have a few moments, but it was mostly pretty dumb, like I wanted. And then I started Touhou Luna Nights, which is a "Metroidvania" fan-game with great pixel art and music.

    OK, I should get back to this vacation!




    ic

    Book Review: WICKED ABANDONED

    Wicked Abandoned A New England Horror Writers Anthology Edited by Rob Smales and Scott T. Goudsward Published by Wicked Creative, LLC  (September 25, 2024) Reviewed by Carson Buckingham All I have to say is that New England sure grows a bunch of great writers! Wicked Abandoned is one of the best anthologies I’ve read in […]

    The post Book Review: WICKED ABANDONED first appeared on Hellnotes.




    ic

    Money, Money, Money (or private affluence and public squalor)

    I sat in my crumbling courthouse a couple of months ago, having edged past the permanently-stuck gate on the justices' car park, and made my way up the nearly-new lift to the assembly room. It is a handsome room, built in 1907 but has sadly not seen a lick of paint in the last decade-and-a-half and more.

    Everywhere are signs of decay and neglect - but no matter. I understand the desperate need for the government to bring expenditure under control, even if that means denying resources to the public service that I have served unpaid these thirty years. There are still biscuits (amazingly) and most of the lights come on when you press a switch. There is some mysterious  kit that we think might be for use in the new all-electronic courthouse. It still bears the protective film that we see on expensive audio visual stuff to protect it on its long journey from a Chinese sweatshop.

    I have recently received an email from  www.gov.uk/annual-tax-summary setting out the tax that I paid in the last fiscal year setting out the tax that I paid (direct tax only, so forget the taxes on consumption such as liquor duties and Council Tax (fifty quid a week on my modest Thames Valley bungalow).

    Much more interesting is the breakdown of where it went, revealing how little our fellow citizens know of what is done with the country's collective cash.

    Not that much goes on the justice system.




    ic

    Supreme Chicken?

    The Supreme Court is now considering a crucial case that will clarify the power of the judiciary vis a vis that of Parliament. Many of the country's finest legal minds will focus on this matter, and a verdict will be handed down. In the long tradition of European matters dividing our nation, some unscrupulous parties are attempting to discredit the Courts, in particular by focusing on individual judges and any perceived bias they may have. This is an appalling piece of vandalism, the worst offender being the Daily Mail. Recently that paper has given space to the risible Ian Duncan Smith, a failed Tory leader.  IDS' opinion reminds us how lucky we were to be spared his presence in Downing Street.

    He repeats the now-customary jibe that judges are unelected. Of course they are, but then so are brain surgeons and airline captains, and we expect and receive a professional and disciplined service from them. Electing judges would fatally damage the public's confidence in the judiciary's utter impartiality.

    We are blessed with a judiciary that is incorruptible, and that is why many foreign litigants choose to have their cases heard in London.

    All judges and magistrates take the same judicial oath:-

     “I, _________ , do swear by Almighty God that I will well and truly serve our Sovereign Lady Queen Elizabeth the Second in the office of ________ , and I will do right to all manner of people after the laws and usages of this realm, without fear or favour, affection or ill will.”

    That's good enough for me.




    ic

    In The Nick

    Prisons are in the news again, following recent outbreaks of disorder. This is an excellent piece from the Telegraph

    I have been to Hollesley Bay a couple of times; it had a completely different culture from closed prisons such as the Scrubs, with a target of getting inmates ready for work on release.




    ic

    Go to our Winter Olympics section




    ic

    Sochi's Winter Olympic preparations 'impressive'

    Ski Sunday presenter Ed Leigh is wowed by Sochi two years ahead of the 2014 Winter Olympics




    ic

    On home ice?

    Why British figure skaters call New Jersey and Florida 'home'




    ic

    GB ice hockey get financial boost

    Great Britain's men's ice hockey team receives a grant from the IOC to help in its bid to qualify for the 2014 Winter Olympics




    ic

    Pizza a Day Diet: Homemade Chicago-style

    Today I went back to the Cook's Illustrated Cookbook for their Chicago-style pizza recipe (No, they're not from Chicago, but their recipe is actually pretty close to others I've used in the past.). 

    They've got a technique where you "laminate" the crust with butter to make it crispier.  It worked well with the sides, but I'm not sure that it quite worked with the bottom, but the crust did turn out pretty firm and full-bodied.  And rich.  Next time I might let it cook a little longer to see what happens.

    The recipe for the sauce and the cheese were a bit different than what I've done before: using shredded mozzarella and diced tomatoes instead of mozzarella slices (or a fresh ball) and crushed tomatoes, but it turned out pretty well.  Next time, though, I think I'll go back to crushed with slices.

    And the Star Trek pizza cutter is actually big enough to use on deep dish...

    I had Brian Yansky and Frances Yansky over to share the results, so I didn't end up taking too many pictures, but here are a couple:

    Pizza! And the Star Trek pizza cutter!
    Frances poses with a slice.

    The cat inspects the table.






     



    • pizza a day
    • Pizza a Day Diet

    ic

    Pizza a Day Diet Archive [January 2015 Edition]: Home Slice Pizza

    Today's ‪#‎PizzaADayDiet‬ occurred at Home Slice Pizza -- Don Tate joined me for the sausage, mushroom, and green pepper pie! This was the thickest thin crust I've had so far, and was sufficient to be not -floppy, yet not doughy, with a good, chewy texture. The cheese was flavorful and the toppings were each present in every bite.


    Altogether, a most excellent pizza -- and they put the leftovers in a tinfoil swan (I've never seen that before in real life :-)).



    • pizza a day
    • Pizza a Day Diet

    ic

    How EELS could change the future of robotic exploration

    The snake-like robot is being designed to autonomously navigate the challenging terrain of Saturn’s moon Enceladus, including descending into fissures in the moon’s icy crust. The skills it needs in order to explore this distant, unfamiliar world may make EELS well equipped to explore even more alien worlds, perhaps including exoplanets.




    ic

    Europa Clipper launches on its journey to Jupiter’s icy moon

    NASA’s Europa Clipper spacecraft launched today aboard a SpaceX Falcon Heavy rocket from NASA’s Kennedy Space Center in Cape Canaveral, Florida.




    ic

    Upgrade for Member Services System

    The Planetary Society is upgrading systems that will offer us many new capabilities and features that will enhance your membership experience.




    ic

    The James Brand × Timex Automatic GMT Watch




    ic

    2026 Cadillac Vistiq Electric SUV



    • SUVs & Trucks

    ic

    Glenmorangie A Tale of Ice Cream Single Malt Scotch Whisky





    ic

    Manscaped The Chairman Pro Electric Shaver




    ic

    1995 Range Rover Classic 300TDI




    ic

    Actually magical.

    Milkweed fluff and magic gold mica — love how these bookends came out. They actually are magical. ????. In the shop!




    ic

    Patrick Stein: Ray Tracing In One Weekend (in Lisp, and n-dimenions)

    Earlier this year, I started working through the online book Ray Tracing In One Weekend (Book 1). I have been following along with it in Common Lisp, and I have been extending it all from 3-dimensional to n-dimensional.

    I reproduced 4-dimensional versions of all of the book images which you can see on my weekend-raytracer github page.

    Here is the final image. This is a 250-samples-per-pixel, 640x360x10 image plane of three large hyperspheres (one mirrored, one diffuse, one glass) atop a very large, diffuse hypersphere. Also atop this very large hypersphere are a bunch of smaller hyperspheres of varying colors and materials. The image is rendered with some defocus-blur.

    Final image of 4-dimensional scene

    Caveat: This depends on a patched version of the policy-cond library that is not in the current Quicklisp distribution but should be in the next.




    ic

    Quicklisp news: October 2024 Quicklisp dist update now available

     New projects: 

    • adp-github — ADP extension to generate github markdown files. — MIT
    • adp-plain — Add Documentation, Please... using plain text. An extension of ADP to generate files with barely additional features. — MIT
    • allioli — Alliolification — MIT
    • alternate-asdf-system-connections — Allows for ASDF system to be connected so that auto-loading may occur. This is a fork of asdf-system-connections and incorporates a load-system-driven mechanism for loading dependencies and also loads the dependencies of the connections. — MIT
    • cbor — CBOR encoder/decoder — MIT
    • charje.documentation — Documentation is an opinionated yet customizable docstring parsing library. — AGPL V3 or any later version
    • chipi — House automation bus in Common Lisp — Apache-2
    • cl-aseprite — Aseprite file format parser — GPLv3
    • cl-astar — A heavily optimized yet flexible A* pathfinding algorithm implementation — MIT
    • cl-ceigen-lite — A Common Lisp wrapper around CEIGEN-LITE - which is itself a C wrapper around the C++ Eigen library. — MIT
    • cl-cf — Computations using continued fractions — GPL-3
    • cl-concord — CONCORD implementation based on Common Lisp — LGPL
    • cl-duckdb — CFFI wrapper around the DuckDB C API — MIT License
    • cl-fastcgi — FastCGI wrapper for Common Lisp — BSD License
    • cl-flx — Rewrite emacs-flx in Common Lisp — MIT
    • cl-frugal-uuid — Common Lisp UUID library with zero dependencies — MIT License
    • cl-gog-galaxy — A wrapper for the GOG Galaxy SDK — zlib
    • cl-lc — List comprehensions — MIT
    • cl-naive-ptrees — Functions to make it easier to work with plist(s) and plist trees. Works with plist(s) pairs as units and not as individual list items. — MIT
    • cl-qoa — An implementation of the Quite Okay Audio format. — zlib
    • cl-reddit — Reddit client api library — BSD
    • cl-resvg — An up-to-date bindings library for the resvg SVG rendering library — zlib
    • cl-trivial-clock — Common Lisp library to get accurate wall-clock times on multiple platforms — MIT License
    • clack-cors — A Clack middleware to set CORS related HTTP headers. — Unlicense
    • clack-prometheus — Clack middleware to serve stats in Prometheus format. — Unlicense
    • clith — Common Lisp wITH macro. A general WITH macro. — MIT
    • clj-arrows — Implements Clojure-styled threading/transformation macros. — MIT
    • clos-encounters — A collection of OOP patterns benefiting from the CLOS MOP. — Unlicense
    • coalton — An efficient, statically typed functional programming language that supercharges Common Lisp. — MIT
    • cocoas — A toolkit library to help deal with CoreFoundation, Cocoa, and objc — zlib
    • com.danielkeogh.graph — A fast an reliable graph library. — MIT
    • fast-mpsc-queue — Multi-Producer Single-Consumer queue implementation. — MIT
    • file-finder — File finder. Enable rapid file search, inspection and manipulation. — GPL3+
    • golden-utils — A utility library. — MIT
    • hiccl — HTML generator for Common Lisp — MIT
    • hsx — Hypertext S-expression — MIT
    • hunchentoot-stuck-connection-monitor — Monitors hunchentoot connections and logs the connections stuck in the same state for a long time (due to slow or inactive clients and network stream timeouts that hunchentoot tries to utilize not working properly). Offers an option to shutdown the stuck connections sockets manually or automatically, thus unblocking the connection threads and preventing thread and socket leak. See https://github.com/edicl/hunchentoot/issues/189 — BSD-2-Clause
    • incless — A portable and extensible Common Lisp printer implementation (core) — BSD
    • inravina — A portable and extensible Common Lisp pretty printer. — MIT
    • invistra — A portable and extensible Common Lisp FORMAT implementation — BSD
    • knx-conn — KNXnet/IP implementation in Common Lisp — GNU GPL, version 3
    • machine-state — Retrieve machine state information about CPU time, memory usage, etc. — zlib
    • myweb — simple web server written in common lisp for educational reasons — LGPLv3
    • noisy — Perlin noise for arbitrary numbers of dimensions. — MIT
    • nontrivial-gray-streams — A compatibility layer for Gray streams including extensions — MIT
    • open-with — Open a file in a suitable external program — zlib
    • openai-openapi-client — Openai API client — AGPLv3+
    • openrpc — CI for Common Lisp OpenRPC library. — BSD
    • parse-number-range — Parses LOOP's convenient "for-as-arithmetic" syntax into 5 simple values: from, to, limit-kind (:inclusive, :exclusive or nil if unbounded), by (step) and direction (+ or -)). Further related utilities are provided. Intended for easy implementation of analogous functionality in other constructs. — Public Domain
    • precise-time — Precise time measurements — zlib
    • pregexp — Portable regular expressions for Common Lisp — MIT-like
    • progressons — Display a progress bar on one line. — MIT
    • quaviver — A portable and extensible floating point string library — MIT
    • quilc — A CLI front-end for the Quil compiler — Apache License 2.0 (See LICENSE.txt)
    • qvm — An implementation of the Quantum Abstract Machine. — Apache License 2.0 (See LICENSE.txt)
    • random-sampling — Functions to generate random samples with various distributions — zlib
    • rs-dlx — Knuth's Algorithm X with dancing links. — Modified BSD License
    • scrapycl — The web scraping framework for writing crawlers in Common Lisp. — Unlicense
    • smoothers — Statistical methods to create approximating functions that attempt to capture important patterns in the data, while leaving out noise or other fine-scale structures/rapid phenomena. — MS-PL
    • trivial-adjust-simple-array — A tiny utility to change array size ensuring it is simple. — MIT
    • trivial-system-loader — A system installation/loading abstraction for Common Lisp — MIT
    • trivial-toplevel-commands — Trivial Toplevel Commands allows to define toplevel commands available on most implementations in a portable fashion. — BSD-3 Clause
    • trivial-toplevel-prompt — Portability library to customize REPL prompts. — BSD-3 Clause
    • utf8-input-stream — A UTF-8 string input stream over a binary stream for Common Lisp — MIT
    • whereiseveryone.command-line-args — Automatically create a command-line-argument parser for a given Common Lisp function definition. — AGPL v3 or any later version

    Updated projects: 3b-bmfont, 3bgl-shader, 3bmd, 3d-math, 3d-spaces, 40ants-asdf-system, 40ants-slynk, access, acclimation, action-list, adhoc, adopt, adp, agnostic-lizard, alexandria, alexandria-plus, anatevka, anypool, april, arc-compat, architecture.builder-protocol, array-utils, arrow-macros, assoc-utils, async-process, atomics, auto-restart, aws-sdk-lisp, babel, bdef, bike, binary-structures, binding-arrows, birch, blackbird, bordeaux-threads, calm, carrier, caveman, ccldoc, cephes.cl, cepl, cerberus, cffi, cffi-object, cffi-ops, chanl, chunga, ci, ci-utils, ciao, cl-6502, cl-algebraic-data-type, cl-all, cl-ansi-term, cl-async, cl-atelier, cl-autowrap, cl-base32, cl-bmas, cl-bmp, cl-bnf, cl-brewer, cl-buchberger, cl-cmark, cl-collider, cl-colors2, cl-confidence, cl-containers, cl-cookie, cl-csv, cl-custom-hash-table, cl-cxx-jit, cl-data-structures, cl-dbi, cl-digraph, cl-dot, cl-enchant, cl-environments, cl-fast-ecs, cl-fbx, cl-fluent-logger, cl-form-types, cl-forms, cl-freetype2, cl-gamepad, cl-github-v3, cl-gltf, cl-gobject-introspection, cl-graph, cl-grip, cl-gserver, cl-hamcrest, cl-hash-util, cl-html-readme, cl-i18n, cl-info, cl-ini, cl-ipfs-api2, cl-kanren, cl-lib-helper, cl-liballegro, cl-liballegro-nuklear, cl-log, cl-markless, cl-marshal, cl-migratum, cl-mixed, cl-modio, cl-mount-info, cl-mpg123, cl-mssql, cl-mustache, cl-mysql, cl-neovim, cl-netpbm, cl-oju, cl-opengl, cl-opensearch-query-builder, cl-opus, cl-patterns, cl-plus-ssl-osx-fix, cl-ppcre, cl-project, cl-protobufs, cl-pslib, cl-pslib-barcode, cl-rashell, cl-readline, cl-sat.minisat, cl-sdl2-image, cl-sdl2-mixer, cl-sdl2-ttf, cl-sendgrid, cl-sentry-client, cl-skkserv, cl-smtp, cl-ssh-keys, cl-steamworks, cl-str, cl-svg, cl-telegram-bot, cl-threadpool, cl-tiled, cl-torrents, cl-tqdm, cl-transducers, cl-transit, cl-unicode, cl-unification, cl-unix-sockets, cl-utils, cl-vectors, cl-vorbis, cl-wavefront, cl-webdriver-client, cl-webkit, cl-webmachine, cl-who, clack, clack-pretend, clad, classimp, clast, clath, clavier, clazy, clerk, clgplot, climacs, clingon, clip, clj-con, clj-re, clobber, clog, clog-ace, clog-collection, clog-plotly, clog-terminal, clohost, closer-mop, clss, cluffer, clunit2, clx, cmd, codata-recommended-values, codex, coleslaw, collectors, colored, com-on, common-lisp-jupyter, commondoc-markdown, compiler-macro-notes, conduit-packages, consfigurator, contextl, croatoan, ctype, cytoscape-clj, damn-fast-priority-queue, dartscluuid, data-frame, data-lens, datafly, dbus, decompress, defenum, definer, definitions, deflate, defmain, deploy, depot, deptree, dexador, dissect, djula, dns-client, doc, docs-builder, dsm, dufy, easter-gauss, easy-audio, easy-macros, easy-routes, eclector, equals, erjoalgo-webutil, erudite, esrap, event-emitter, external-program, external-symbol-not-found, fare-csv, fare-scripts, fast-http, fast-websocket, file-attributes, file-notify, file-select, filesystem-utils, fiveam, fiveam-matchers, flexi-streams, float-features, flow, fn, fset, functional-trees, fuzzy-dates, gadgets, generic-cl, github-api-cl, glfw, glsl-toolkit, harmony, hashtrie, helambdap, http2, hunchentoot, imago, in-nomine, inferior-shell, introspect-environment, ironclad, jose, js, json-mop, jsonrpc, jzon, khazern, lack, lass, lemmy-api, letv, lichat-protocol, lichat-tcp-client, linear-programming, lisp-binary, lisp-chat, lisp-critic, lisp-pay, lisp-stat, lispcord, lla, local-time, log4cl-extras, logging, lru-cache, magicl, maiden, maidenhead, manifolds, math, mcclim, memory-regions, messagebox, method-combination-utilities, mgl-pax, misc-extensions, mito, mk-defsystem, mmap, mnas-package, mnas-string, moira, multiposter, mutility, mutils, named-closure, ndebug, neural-classifier, new-op, nibbles, nibbles-streams, ningle, nodgui, north, numerical-utilities, nytpu.lisp-utils, omglib, ook, open-location-code, openapi-generator, orizuru-orm, overlord, papyrus, parachute, parse-number, pathname-utils, petalisp, phos, picl, plot, plump, plump-sexp, pngload, policy-cond, polymorphic-functions, postmodern, ppath, prometheus-gc, psychiq, purgatory, py4cl, py4cl2, py4cl2-cffi, qlot, qoi, query-fs, quick-patch, quickhull, quri, random-state, reblocks, reblocks-auth, reblocks-file-server, reblocks-lass, reblocks-navigation-widget, reblocks-parenscript, reblocks-prometheus, reblocks-typeahead, reblocks-ui, reblocks-websocket, rove, s-dot2, sandalphon.lambda-list, sb-fastcgi, sc-extensions, sel, select, serapeum, shasht, shop3, si-kanren, sketch, slime, slite, sly, snooze, spinneret, staple, static-vectors, statistics, stepster, stmx, stripe, swank-crew, swank-protocol, sxql, symath, system-locale, taglib, teddy, ten, testiere, tfeb-lisp-hax, tfm, tiny-routes, tooter, trivia, trivial-arguments, trivial-clipboard, trivial-file-size, trivial-gray-streams, trivial-main-thread, trivial-octet-streams, trivial-package-locks, trivial-package-manager, trivial-sanitize, trivial-shell, type-templates, typo, uax-15, uiop, usocket, vellum, vellum-binary, vellum-csv, vellum-postmodern, verbose, vernacular, vom, websocket-driver, winhttp, with-branching, with-contexts, woo, xhtmlambda, xml-emitter, yason, zippy, zpb-ttf.

    Removed projects: abstract-arrays, ahungry-fleece, cl-cheshire-cat, cl-darksky, cl-epoch, cl-naive-store, convolution-kernel, dense-arrays, extensible-compound-types, extensible-optimizing-coerce, fast-generic-functions, flac-metadata, freebsd-ffi, listoflist, luckless, one-more-re-nightmare, postmodern-localtime, stumpwm-dynamic-float, stumpwm-sndioctl, unicly.

    To get this update, use:

     (ql:update-dist "quicklisp")

    Sorry this update took so long. My goal is to resume monthly releases.

    Enjoy!




    ic

    TurtleWare: Dynamic Vars - A New Hope

    Table of Contents

    1. Dynamic Bindings
    2. The problem
    3. The solution
    4. Dynamic slots
    5. The context
    6. Summary

    Dynamic Bindings

    Common Lisp has an important language feature called dynamic binding. It is possible to rebind a dynamic variable somewhere on the call stack and downstream functions will see that new value, and when the stack is unwound, the old value is brought back.

    While Common Lisp does not specify multi-threading, it seems to be a consensus among various implementations that dynamic bindings are thread-local, allowing for controlling the computing context in a safe way.

    Before we start experiments, let's define a package to isolate our namespace:

    (defpackage "EU.TURTLEWARE.BLOG/DLET"
      (:local-nicknames ("MOP" #+closer-mop "C2MOP"
                               #+(and (not closer-mop) ecl) "MOP"
                               #+(and (not closer-mop) ccl) "CCL"
                               #+(and (not closer-mop) sbcl) "SB-MOP"))
      (:use "CL"))
    (in-package "EU.TURTLEWARE.BLOG/DLET")
    

    Dynamic binding of variables is transparent to the programmer, because the operator LET is used for both lexical and dynamic bindings. For example:

    (defvar *dynamic-variable* 42)
    
    (defun test ()
      (let ((*dynamic-variable* 15)
            (lexical-variable 12))
        (lambda ()
          (print (cons *dynamic-variable* lexical-variable)))))
    
    (funcall (test))
    ;;; (42 . 12)
    
    (let ((*dynamic-variable* 'xx))
      (funcall (test)))
    ;;; (xx . 12)
    

    Additionally the language specifies a special operator PROGV that gives the programmer a control over the dynamic binding mechanism, by allowing passing the dynamic variable by value instead of its name. Dynamic variables are represented by symbols:

    (progv (list '*dynamic-variable*) (list 'zz)
      (funcall (test)))
    ;;; (zz . 12)
    

    The problem

    Nowadays it is common to encapsulate the state in the instance of a class. Sometimes that state is dynamic. It would be nice if we could use dynamic binding to control it. That said slots are not variables, and if there are many objects of the same class with different states, then using dynamic variables defined with DEFVAR is not feasible.

    Consider the following classes which we want to be thread-safe:

    (defgeneric call-with-ink (cont window ink))
    
    (defclass window-1 ()
      ((ink :initform 'red :accessor ink)))
    
    (defmethod call-with-ink (cont (win window-1) ink)
      (let ((old-ink (ink win)))
        (setf (ink win) ink)
        (unwind-protect (funcall cont)
          (setf (ink win) old-ink))))
    
    (defclass window-2 ()
      ())
    
    (defvar *ink* 'blue)
    (defmethod ink ((window window-2)) *ink*)
    
    (defmethod call-with-ink (cont (win window-2) ink)
      (let ((*ink* ink))
        (funcall cont)))
    

    The first example is clearly not thread safe. If we access the WINDOW-1 instance from multiple threads, then they will overwrite a value of the slot INK.

    The second example is not good either, because when we have many instances of WINDOW-2 then they share the binding. Nesting CALL-WITH-INK will overwrite the binding of another window.

    The solution

    The solution is to use PROGV:

    (defclass window-3 ()
      ((ink :initform (gensym))))
    
    (defmethod initialize-instance :after ((win window-3) &key)
      (setf (symbol-value (slot-value win 'ink)) 'red))
    
    (defmethod call-with-ink (cont (win window-3) ink)
      (progv (list (slot-value win 'ink)) (list ink)
        (funcall cont)))
    

    This way each instance has its own dynamic variable that may be rebound with a designated operator CALL-WITH-INK. It is thread-safe and private. We may add some syntactic sugar so it is more similar to let:

    (defmacro dlet (bindings &body body)
      (loop for (var val) in bindings
            collect var into vars
            collect val into vals
            finally (return `(progv (list ,@vars) (list ,@vals)
                               ,@body))))
    
    (defmacro dset (&rest pairs)
      `(setf ,@(loop for (var val) on pairs by #'cddr
                     collect `(symbol-value ,var)
                     collect val)))
    
    (defmacro dref (variable)
      `(symbol-value ,variable))
    

    Dynamic slots

    While meta-classes are not easily composable, it is worth noting that we can mold it better into the language by specifying that slot itself has a dynamic value. This way CLOS aficionados will have a new tool in their arsenal.

    The approach we'll take is that a fresh symbol is stored as the value of each instance-allocated slot, and then accessors for the slot value will use these symbols as a dynamic variable. Here are low-level accessors:

    ;;; Accessing and binding symbols behind the slot. We don't use SLOT-VALUE,
    ;;; because it will return the _value_ of the dynamic variable, and not the
    ;;; variable itself.
    (defun slot-dvar (object slotd)
      (mop:standard-instance-access
       object (mop:slot-definition-location slotd)))
    
    (defun slot-dvar* (object slot-name)
      (let* ((class (class-of object))
             (slotd (find slot-name (mop:class-slots class)
                          :key #'mop:slot-definition-name)))
        (slot-dvar object slotd)))
    
    (defmacro slot-dlet (bindings &body body)
      `(dlet ,(loop for ((object slot-name) val) in bindings
                     collect `((slot-dvar* ,object ,slot-name) ,val))
         ,@body))
    

    Now we'll define the meta-class. We need that to specialize functions responsible for processing slot definitions and the instance allocation. Notice, that we make use of a kludge to communicate between COMPUTE-EFFECTIVE-SLOT-DEFINITION and EFFECTIVE-SLOT-DEFINITION-CLASS – this is because the latter has no access to the direct slot definitions.

    ;;; The metaclass CLASS-WITH-DYNAMIC-SLOTS specifies alternative effective slot
    ;;; definitions for slots with an initarg :dynamic.
    (defclass class-with-dynamic-slots (standard-class) ())
    
    ;;; Class with dynamic slots may be subclasses of the standard class.
    (defmethod mop:validate-superclass ((class class-with-dynamic-slots)
                                        (super standard-class))
      t)
    
    ;;; When allocating the instance we initialize all slots to a fresh symbol that
    ;;; represents the dynamic variable.
    (defmethod allocate-instance ((class class-with-dynamic-slots) &rest initargs)
      (declare (ignore initargs))
      (let ((object (call-next-method)))
        (loop for slotd in (mop:class-slots class)
              when (typep slotd 'dynamic-effective-slot) do
                (setf (mop:standard-instance-access
                       object
                       (mop:slot-definition-location slotd))
                      (gensym (string (mop:slot-definition-name slotd)))))
        object))
    
    ;;; To improve potential composability of CLASS-WITH-DYNAMIC-SLOTS with other
    ;;; metaclasses we treat specially only slots that has :DYNAMIC in initargs,
    ;;; otherwise we call the next method.
    (defmethod mop:direct-slot-definition-class
        ((class class-with-dynamic-slots) &rest initargs)
      (loop for (key val) on initargs by #'cddr
            when (eq key :dynamic)
              do (return-from mop:direct-slot-definition-class
                   (find-class 'dynamic-direct-slot)))
      (call-next-method))
    
    ;;; The metaobject protocol did not specify an elegant way to communicate
    ;;; between the direct slot definition and the effective slot definition.
    ;;; Luckily we have dynamic bindings! :-)
    (defvar *kludge/mop-deficiency/dynamic-slot-p* nil)
    (defmethod mop:compute-effective-slot-definition
        ((class class-with-dynamic-slots)
         name
         direct-slotds)
      (if (typep (first direct-slotds) 'dynamic-direct-slot)
          (let* ((*kludge/mop-deficiency/dynamic-slot-p* t))
            (call-next-method))
          (call-next-method)))
    
    (defmethod mop:effective-slot-definition-class
        ((class class-with-dynamic-slots) &rest initargs)
      (declare (ignore initargs))
      (if *kludge/mop-deficiency/dynamic-slot-p*
          (find-class 'dynamic-effective-slot)
          (call-next-method)))
    

    Finally we define a direct and an effective slot classes, and specialize slot accessors that are invoked by the instance accessors.

    ;;; There is a considerable boilerplate involving customizing slots.
    ;;;
    ;;; - direct slot definition: local to a single defclass form
    ;;;
    ;;; - effective slot definition: combination of all direct slots with the same
    ;;;   name in the class and its superclasses
    ;;;
    (defclass dynamic-direct-slot (mop:standard-direct-slot-definition)
      ((dynamic :initform nil :initarg :dynamic :reader dynamic-slot-p)))
    
    ;;; DYNAMIC-EFFECTIVE-SLOT is implemented to return as slot-value values of the
    ;;; dynamic variable that is stored with the instance.
    ;;;
    ;;; It would be nice if we could specify :ALLOCATION :DYNAMIC for the slot, but
    ;;; then STANDARD-INSTANCE-ACCESS would go belly up. We could make a clever
    ;;; workaround, but who cares?
    (defclass dynamic-effective-slot (mop:standard-effective-slot-definition)
      ())
    
    (defmethod mop:slot-value-using-class
        ((class class-with-dynamic-slots)
         object
         (slotd dynamic-effective-slot))
      (dref (slot-dvar object slotd)))
    
    (defmethod (setf mop:slot-value-using-class)
        (new-value
         (class class-with-dynamic-slots)
         object
         (slotd dynamic-effective-slot))
      (dset (slot-dvar object slotd) new-value))
    
    (defmethod mop:slot-boundp-using-class
      ((class class-with-dynamic-slots)
       object
       (slotd dynamic-effective-slot))
      (boundp (slot-dvar object slotd)))
    
    (defmethod mop:slot-makunbound-using-class
      ((class class-with-dynamic-slots)
       object
       (slotd dynamic-effective-slot))
      (makunbound (slot-dvar object slotd)))
    

    With this, we can finally define a class with slots that have dynamic values. What's more, we may bind them like dynamic variables.

    ;;; Let there be light.
    (defclass window-4 ()
      ((ink :initform 'red :dynamic t :accessor ink)
       (normal :initform 'normal :accessor normal))
      (:metaclass class-with-dynamic-slots))
    
    (let ((object (make-instance 'window-4)))
      (slot-dlet (((object 'ink) 15))
        (print (ink object)))
      (print (ink object)))
    

    ContextL provides a similar solution with dynamic slots, although it provides much more, like layered classes. This example is much more self-contained.

    The context

    Lately I'm working on the repaint queue for McCLIM. While doing so I've decided to make stream operations thread-safe, so it is possible to draw on the stream and write to it from arbitrary thread asynchronously. The access to the output record history needs to be clearly locked, so that may be solved by the mutex. Graphics state is another story, consider the following functions running from separate threads:

    (defun team-red ()
      (with-drawing-options (stream :ink +dark-red+)
        (loop for i from 0 below 50000 do
          (write-string (format nil "XXX: ~5d~%" i) stream))))
    
    (defun team-blue ()
      (with-drawing-options (stream :ink +dark-blue+)
        (loop for i from 0 below 50000 do
          (write-string (format nil "YYY: ~5d~%" i) stream))))
    
    (defun team-pink ()
      (with-drawing-options (stream :ink +deep-pink+)
        (loop for i from 0 below 25000 do
          (case (random 2)
            (0 (draw-rectangle* stream 200 (* i 100) 250 (+ (* i 100) 50)))
            (1 (draw-circle* stream 225 (+ (* i 100) 25) 25))))))
    
    (defun gonow (stream)
      (window-clear stream)
      (time (let ((a (clim-sys:make-process #'team-red))
                  (b (clim-sys:make-process #'team-blue))
                  (c (clim-sys:make-process #'team-grue)))
              (bt:join-thread a)
              (bt:join-thread b)
              (bt:join-thread c)
              (format stream "done!~%")))  )
    

    Operations like WRITE-STRING and DRAW-RECTANGLE can be implemented by holding a lock over the shared resource without much disruption. The drawing color on the other hand is set outside of the loop, so if we had locked the graphics state with a lock, then these functions would be serialized despite being called from different processes. The solution to this problem is to make graphics context a dynamic slot that is accessed with WITH-DRAWING-OPTIONS.

    Summary

    I hope that I've convinced you that dynamic variables are cool (I'm sure that majority of readers here are already convinced), and that dynamic slots are even cooler :-). Watch forward to the upcoming McCLIM release!

    If you like technical writeups like this, please consider supporting me on Patreon.




    ic

    TurtleWare: Dynamic Vars - The Empire Strikes Back

    Table of Contents

    1. Thread Local storage exhausted
    2. The layer of indirection
    3. I can fix her
    4. Let's write some tests!
    5. Summary

    Thread Local storage exhausted

    In the last post I've described a technique to use dynamic variables by value instead of the name by utilizing the operator PROGV. Apparently it works fine on all Common Lisp implementations I've tried except from SBCL, where the number of thread local variables is by default limited to something below 4000. To add salt to the injury, these variables are not garbage collected.

    Try the following code to crash into LDB:

    (defun foo ()
      (loop for i from 0 below 4096 do
        (when (zerop (mod i 100))
          (print i))
        (progv (list (gensym)) (list 42)
          (values))))
    (foo)
    

    This renders our new technique not very practical given SBCL popularity. We need to either abandon the idea or come up with a workaround.

    The layer of indirection

    Luckily for us we've already introduced a layer of indirection. Operators to access dynamic variables are called DLET, DSET and DREF. This means, that it is enough to provide a kludge implementation for SBCL with minimal changes to the remaining code.

    The old code works the same as previously except that instead of SYMBOL-VALUE we use the accessor DYNAMIC-VARIABLE-VALUE, and the old call to PROGV is now DYNAMIC-VARIABLE-PROGV. Moreover DYNAMIC-EFFECTIVE-SLOT used functions BOUNDP and MAKUNBOUND, so we replace these with DYNAMIC-VARIABLE-BOUND-P and DYNAMIC-VARIABLE-MAKUNBOUND. To abstract away things further we also introduce the constructor MAKE-DYNAMIC-VARIABLE

    (defpackage "EU.TURTLEWARE.BLOG/DLET"
      (:local-nicknames ("MOP" #+closer-mop "C2MOP"
                               #+(and (not closer-mop) ecl) "MOP"
                               #+(and (not closer-mop) ccl) "CCL"
                               #+(and (not closer-mop) sbcl) "SB-MOP"))
      (:use "CL"))
    (in-package "EU.TURTLEWARE.BLOG/DLET")
    
    (eval-when (:compile-toplevel :execute :load-toplevel)
      (unless (member :bordeaux-threads *features*)
        (error "Please load BORDEAUX-THREADS."))
      (when (member :sbcl *features*)
        (unless (member :fake-progv-kludge *features*)
          (format t "~&;; Using FAKE-PROGV-KLUDGE for SBCL.~%")
          (push :fake-progv-kludge *features*))))
    
    (defmacro dlet (bindings &body body)
      (flet ((pred (binding)
               (and (listp binding) (= 2 (length binding)))))
        (unless (every #'pred bindings)
          (error "DLET: bindings must be lists of two values.~%~
                    Invalid bindings:~%~{ ~s~%~}" (remove-if #'pred bindings))))
      (loop for (var val) in bindings
            collect var into vars
            collect val into vals
            finally (return `(dynamic-variable-progv (list ,@vars) (list ,@vals)
                               ,@body))))
    
    (defmacro dset (&rest pairs)
      `(setf ,@(loop for (var val) on pairs by #'cddr
                     collect `(dref ,var)
                     collect val)))
    
    (defmacro dref (variable)
      `(dynamic-variable-value ,variable))
    
    ;;; ...
    
    (defmethod mop:slot-boundp-using-class
        ((class standard-class)
         object
         (slotd dynamic-effective-slot))
      (dynamic-variable-bound-p (slot-dvar object slotd)))
    
    (defmethod mop:slot-makunbound-using-class
        ((class standard-class)
         object
         (slotd dynamic-effective-slot))
      (dynamic-variable-makunbound (slot-dvar object slotd)))
    

    With these in place we can change the portable implementation to conform.

    #-fake-progv-kludge
    (progn
      (defun make-dynamic-variable ()
        (gensym))
    
      (defun dynamic-variable-value (variable)
        (symbol-value variable))
    
      (defun (setf dynamic-variable-value) (value variable)
        (setf (symbol-value variable) value))
    
      (defun dynamic-variable-bound-p (variable)
        (boundp variable))
    
      (defun dynamic-variable-makunbound (variable)
        (makunbound variable))
    
      (defmacro dynamic-variable-progv (vars vals &body body)
        `(progv ,vars ,vals ,@body)))
    

    I can fix her

    The implementation for SBCL will mediate access to the dynamic variable value with a synchronized hash table with weak keys. The current process is the key of the hash table and the list of bindings is the value of the hash table. For compatibility between implementations the top level value of the symbol will be shared.

    The variable +FAKE-UNBOUND+ is the marker that signifies, that the variable has no value. When the list of bindings is EQ to +CELL-UNBOUND+, then it means that we should use the global value. We add new bindings by pushing to it.

    #+fake-progv-kludge
    (progn
      (defvar +fake-unbound+ 'unbound)
      (defvar +cell-unbound+ '(no-binding))
    
      (defclass dynamic-variable ()
        ((tls-table
          :initform (make-hash-table :synchronized t :weakness :key)
          :reader dynamic-variable-tls-table)
         (top-value
          :initform +fake-unbound+
          :accessor dynamic-variable-top-value)))
    
      (defun make-dynamic-variable ()
        (make-instance 'dynamic-variable))
    
      (defun dynamic-variable-bindings (dvar)
        (let ((process (bt:current-thread))
              (tls-table (dynamic-variable-tls-table dvar)))
          (gethash process tls-table +cell-unbound+)))
    
      (defun (setf dynamic-variable-bindings) (value dvar)
        (let ((process (bt:current-thread))
              (tls-table (dynamic-variable-tls-table dvar)))
          (setf (gethash process tls-table +cell-unbound+) value))))
    

    We define two readers for the variable value - one that simply reads the value, and the other that signals an error if the variable is unbound. Writer for its value either replaces the current binding, or if the value cell is unbound, then we modify the top-level symbol value. We use the value +FAKE-UNBOUND+ to check whether the variable is bound and to make it unbound.

    #+fake-progv-kludge
    (progn
      (defun %dynamic-variable-value (dvar)
        (let ((tls-binds (dynamic-variable-bindings dvar)))
          (if (eq tls-binds +cell-unbound+)
              (dynamic-variable-top-value dvar)
              (car tls-binds))))
    
      (defun dynamic-variable-value (dvar)
        (let ((tls-value (%dynamic-variable-value dvar)))
          (when (eq tls-value +fake-unbound+)
            (error 'unbound-variable :name "(unnamed)"))
          tls-value))
    
      (defun (setf dynamic-variable-value) (value dvar)
        (let ((tls-binds (dynamic-variable-bindings dvar)))
          (if (eq tls-binds +cell-unbound+)
              (setf (dynamic-variable-top-value dvar) value)
              (setf (car tls-binds) value))))
    
      (defun dynamic-variable-bound-p (dvar)
        (not (eq +fake-unbound+ (%dynamic-variable-value dvar))))
    
      (defun dynamic-variable-makunbound (dvar)
        (setf (dynamic-variable-value dvar) +fake-unbound+)))
    

    Finally we define the operator to dynamically bind variables that behaves similar to PROGV. Note that we PUSH and POP from the thread-local hash table DYNAMIC-VARIABLE-BINDINGS, so no synchronization is necessary.

    #+fake-progv-kludge
    (defmacro dynamic-variable-progv (vars vals &body body)
      (let ((svars (gensym))
            (svals (gensym))
            (var (gensym))
            (val (gensym)))
        `(let ((,svars ,vars))
           (loop for ,svals = ,vals then (rest ,svals)
                 for ,var in ,svars
                 for ,val = (if ,svals (car ,svals) +fake-unbound+)
                 do (push ,val (dynamic-variable-bindings ,var)))
           (unwind-protect (progn ,@body)
             (loop for ,var in ,svars
                   do (pop (dynamic-variable-bindings ,var)))))))
    

    Let's write some tests!

    But of course, we are going to also write a test framework. It's short, I promise. As a bonus point the API is compatibile with fiveam, so it is possible to drop tests as is in the appropriate test suite.

    (defvar *all-tests* '())
    
    (defun run-tests ()
      (dolist (test (reverse *all-tests*))
        (format *debug-io* "Test ~a... " test)
        (handler-case (funcall test)
          (serious-condition (c)
            (format *debug-io* "Failed: ~a~%" c))
          (:no-error (&rest args)
            (declare (ignore args))
            (format *debug-io* "Passed.~%")))))
    
    (defmacro test (name &body body)
      `(progn
         (pushnew ',name *all-tests*)
         (defun ,name () ,@body)))
    
    (defmacro is (form)
      `(assert ,form))
    
    (defmacro pass ())
    
    (defmacro signals (condition form)
      `(is (block nil
             (handler-case ,form
               (,condition () (return t)))
             nil)))
    
    (defmacro finishes (form)
      `(is (handler-case ,form
             (serious-condition (c)
               (declare (ignore c))
               nil)
             (:no-error (&rest args)
               (declare (ignore args))
               t))))
    

    Now let's get to tests. First we'll test our metaclass:

    (defclass dynamic-let.test-class ()
      ((slot1 :initarg :slot1 :dynamic nil :accessor slot1)
       (slot2 :initarg :slot2 :dynamic t   :accessor slot2)
       (slot3 :initarg :slot3              :accessor slot3))
      (:metaclass class-with-dynamic-slots))
    
    (defparameter *dynamic-let.test-instance-1*
      (make-instance 'dynamic-let.test-class
                     :slot1 :a :slot2 :b :slot3 :c))
    
    (defparameter *dynamic-let.test-instance-2*
      (make-instance 'dynamic-let.test-class
                     :slot1 :x :slot2 :y :slot3 :z))
    
    (test dynamic-let.1
      (let ((o1 *dynamic-let.test-instance-1*)
            (o2 *dynamic-let.test-instance-2*))
        (with-slots (slot1 slot2 slot3) o1
          (is (eq :a slot1))
          (is (eq :b slot2))
          (is (eq :c slot3)))
        (with-slots (slot1 slot2 slot3) o2
          (is (eq :x slot1))
          (is (eq :y slot2))
          (is (eq :z slot3)))))
    
    (test dynamic-let.2
      (let ((o1 *dynamic-let.test-instance-1*)
            (o2 *dynamic-let.test-instance-2*))
        (signals error (slot-dlet (((o1 'slot1) 1)) nil))
        (slot-dlet (((o1 'slot2) :k))
          (is (eq :k (slot-value o1 'slot2)))
          (is (eq :y (slot-value o2 'slot2))))))
    
    (test dynamic-let.3
      (let ((o1 *dynamic-let.test-instance-1*)
            (exit nil)
            (fail nil))
        (flet ((make-runner (values)
                 (lambda ()
                   (slot-dlet (((o1 'slot2) :start))
                     (let ((value (slot2 o1)))
                       (unless (eq value :start)
                         (setf fail value)))
                     (loop until (eq exit t) do
                       (setf (slot2 o1) (elt values (random (length values))))
                       (let ((value (slot2 o1)))
                         (unless (member value values)
                           (setf fail value)
                           (setf exit t))))))))
          (let ((r1 (bt:make-thread (make-runner '(:k1 :k2))))
                (r2 (bt:make-thread (make-runner '(:k3 :k4))))
                (r3 (bt:make-thread (make-runner '(:k5 :k6)))))
            (sleep .1)
            (setf exit t)
            (map nil #'bt:join-thread (list r1 r2 r3))
            (is (eq (slot2 o1) :b))
            (is (null fail))))))
    

    Then let's test the dynamic variable itself:

    (test dynamic-let.4
      "Test basic dvar operators."
      (let ((dvar (make-dynamic-variable)))
        (is (eql 42 (dset dvar 42)))
        (is (eql 42 (dref dvar)))
        (ignore-errors
         (dlet ((dvar :x))
           (is (eql :x (dref dvar)))
           (error "foo")))
        (is (eql 42 (dref dvar)))))
    
    (test dynamic-let.5
      "Test bound-p operator."
      (let ((dvar (make-dynamic-variable)))
        (is (not (dynamic-variable-bound-p dvar)))
        (dset dvar 15)
        (is (dynamic-variable-bound-p dvar))
        (dynamic-variable-makunbound dvar)
        (is (not (dynamic-variable-bound-p dvar)))))
    
    (test dynamic-let.6
      "Test makunbound operator."
      (let ((dvar (make-dynamic-variable)))
        (dset dvar t)
        (is (dynamic-variable-bound-p dvar))
        (finishes (dynamic-variable-makunbound dvar))
        (is (not (dynamic-variable-bound-p dvar)))))
    
    (test dynamic-let.7
      "Test locally bound-p operator."
      (let ((dvar (make-dynamic-variable)))
        (is (not (dynamic-variable-bound-p dvar)))
        (dlet ((dvar 15))
          (is (dynamic-variable-bound-p dvar)))
        (is (not (dynamic-variable-bound-p dvar)))))
    
    (test dynamic-let.8
      "Test locally unbound-p operator."
      (let ((dvar (make-dynamic-variable)))
        (dset dvar t)
        (is (dynamic-variable-bound-p dvar))
        (dlet ((dvar nil))
          (is (dynamic-variable-bound-p dvar))
          (finishes (dynamic-variable-makunbound dvar))
          (is (not (dynamic-variable-bound-p dvar))))
        (is (dynamic-variable-bound-p dvar))))
    
    (test dynamic-let.9
      "Stress test the implementation (see :FAKE-PROGV-KLUDGE)."
      (finishes                              ; at the same time
        (let ((dvars (loop repeat 4096 collect (make-dynamic-variable))))
          ;; ensure tls variable
          (loop for v in dvars do
            (dlet ((v 1))))
          (loop for i from 0 below 4096
                for r = (random 4096)
                for v1 in dvars
                for v2 = (elt dvars r) do
                  (when (zerop (mod i 64))
                    (pass))
                  (dlet ((v1 42)
                         (v2 43))
                    (values))))))
    
    (test dynamic-let.0
      "Stress test the implementation (see :FAKE-PROGV-KLUDGE)."
      (finishes                             ; can be gc-ed
        (loop for i from 0 below 4096 do
          (when (zerop (mod i 64))
            (pass))
          (dlet (((make-dynamic-variable) 42))
            (values)))))
    

    All that is left is to test both dynamic variable implementations:

    BLOG/DLET> (lisp-implementation-type)
    "ECL"
    BLOG/DLET> (run-tests)
    Test DYNAMIC-LET.1... Passed.
    Test DYNAMIC-LET.2... Passed.
    Test DYNAMIC-LET.3... Passed.
    Test DYNAMIC-LET.4... Passed.
    Test DYNAMIC-LET.5... Passed.
    Test DYNAMIC-LET.6... Passed.
    Test DYNAMIC-LET.7... Passed.
    Test DYNAMIC-LET.8... Passed.
    Test DYNAMIC-LET.9... Passed.
    Test DYNAMIC-LET.0... Passed.
    NIL
    

    And with the kludge:

    BLOG/DLET> (lisp-implementation-type)
    "SBCL"
    BLOG/DLET> (run-tests)
    Test DYNAMIC-LET.1... Passed.
    Test DYNAMIC-LET.2... Passed.
    Test DYNAMIC-LET.3... Passed.
    Test DYNAMIC-LET.4... Passed.
    Test DYNAMIC-LET.5... Passed.
    Test DYNAMIC-LET.6... Passed.
    Test DYNAMIC-LET.7... Passed.
    Test DYNAMIC-LET.8... Passed.
    Test DYNAMIC-LET.9... Passed.
    Test DYNAMIC-LET.0... Passed.
    NIL
    

    Summary

    In this post we've made our implementation to work on SBCL even when there are more than a few thousand dynamic variables. We've also added a simple test suite that checks the basic behavior.

    As it often happens, after achieving some goal we get greedy and achieve more. That's the case here as well. In the next (and the last) post in this series I'll explore the idea of adding truly thread-local variables without a shared global value. This will be useful for lazily creating context on threads that are outside of our control. We'll also generalize the implementation so it is possible to subclass and implement ones own flavor of a dynamic variable.




    ic

    TurtleWare: Dynamic Vars - Return of the Jedi

    Table of Contents

    1. The protocol
    2. Control operators
    3. Synchronized hash tables with weakness
    4. First-class dynamic variables
      1. STANDARD-DYNAMIC-VARIABLE
      2. SURROGATE-DYNAMIC-VARIABLE
    5. Thread-local variables
      1. The protocol
      2. The implementation
    6. Thread-local slots
    7. What can we use it for?

    In the previous two posts I've presented an implementation of first-class dynamic variables using PROGV and a surrogate implementation for SBCL.

    Now we will double down on this idea and make the protocol extensible. Finally we'll implement a specialized version of dynamic variables where even the top level value of the variable is thread-local.

    The protocol

    Previously we've defined operators as either macros or functions. Different implementations were protected by the feature flag and symbols collided. Now we will introduce the protocol composed of a common superclass and functions that are specialized by particular implementations.

    Most notably we will introduce a new operator CALL-WITH-DYNAMIC-VARIABLE that is responsible for establishing a single binding. Thanks to that it will be possible to mix dynamic variables of different types within a single DLET statement.

    (defclass dynamic-variable () ())
    
    (defgeneric dynamic-variable-bindings (dvar))
    (defgeneric dynamic-variable-value (dvar))
    (defgeneric (setf dynamic-variable-value) (value dvar))
    (defgeneric dynamic-variable-bound-p (dvar))
    (defgeneric dynamic-variable-makunbound (dvar))
    (defgeneric call-with-dynamic-variable (cont dvar &optional value))
    

    Moreover we'll define a constructor that is specializable by a key. This design will allow us to refer to the dynamic variable class by using a shorter name. We will also define the standard class to be used and an matching constructor.

    (defparameter *default-dynamic-variable-class*
      #-fake-progv-kludge 'standard-dynamic-variable
      #+fake-progv-kludge 'surrogate-dynamic-variable)
    
    (defgeneric make-dynamic-variable-using-key (key &rest initargs)
      (:method (class &rest initargs)
        (apply #'make-instance class initargs))
      (:method ((class (eql t)) &rest initargs)
        (apply #'make-instance *default-dynamic-variable-class* initargs))
      (:method ((class null) &rest initargs)
        (declare (ignore class initargs))
        (error "Making a dynamic variable that is not, huh?")))
    
    (defun make-dynamic-variable (&rest initargs)
      (apply #'make-dynamic-variable-using-key t initargs))
    

    Control operators

    Control operators are the same as previously, that is a set of four macros that consume the protocol specified above. Note that DYNAMIC-VARIABLE-PROGV expands to a recursive call where each binding is processed separately.

    (defmacro dlet (bindings &body body)
      (flet ((pred (binding)
               (and (listp binding) (= 2 (length binding)))))
        (unless (every #'pred bindings)
          (error "DLET: bindings must be lists of two values.~%~
                  Invalid bindings:~%~{ ~s~%~}" (remove-if #'pred bindings))))
      (loop for (var val) in bindings
            collect var into vars
            collect val into vals
            finally (return `(dynamic-variable-progv (list ,@vars) (list ,@vals)
                               ,@body))))
    
    (defmacro dset (&rest pairs)
      `(setf ,@(loop for (var val) on pairs by #'cddr
                     collect `(dref ,var)
                     collect val)))
    
    (defmacro dref (variable)
      `(dynamic-variable-value ,variable))
    
    (defun call-with-dynamic-variable-progv (cont vars vals)
      (flet ((thunk ()
               (if vals
                   (call-with-dynamic-variable cont (car vars) (car vals))
                   (call-with-dynamic-variable cont (car vars)))))
        (if vars
            (call-with-dynamic-variable-progv #'thunk (cdr vars) (cdr vals))
            (funcall cont))))
    
    (defmacro dynamic-variable-progv (vars vals &body body)
      (let ((cont (gensym)))
        `(flet ((,cont () ,@body))
           (call-with-dynamic-variable-progv (function ,cont) ,vars ,vals))))
    

    Synchronized hash tables with weakness

    Previously we've used SBCL-specific options to define a synchronized hash table with weak keys. This won't do anymore, because we will need a similar object to implement the thread-local storage for top level values.

    trivial-garbage is a portability layer that allows to define hash tables with a specified weakness, but it does not provide an argument that would abstract away synchronization. We will ensure thread-safety with locks instead.

    (defclass tls-table ()
      ((table :initform (trivial-garbage:make-weak-hash-table
                         :test #'eq :weakness :key))
       (lock :initform (bt:make-lock))))
    
    (defun make-tls-table ()
      (make-instance 'tls-table))
    
    (defmacro with-tls-table ((var self) &body body)
      (let ((obj (gensym)))
        `(let* ((,obj ,self)
                (,var (slot-value ,obj 'table)))
           (bt:with-lock-held ((slot-value ,obj 'lock)) ,@body))))
    

    First-class dynamic variables

    STANDARD-DYNAMIC-VARIABLE

    Previously in the default implementation we've represented dynamic variables with a symbol. The new implementation is similar except that the symbol is read from a STANDARD-OBJECT that represents the variable. This also enables us to specialize the function CALL-WITH-DYNAMIC-VARIABLE:

    (defclass standard-dynamic-variable (dynamic-variable)
      ((symbol :initform (gensym) :accessor dynamic-variable-bindings)))
    
    (defmethod dynamic-variable-value ((dvar standard-dynamic-variable))
      (symbol-value (dynamic-variable-bindings dvar)))
    
    (defmethod (setf dynamic-variable-value) (value (dvar standard-dynamic-variable))
      (setf (symbol-value (dynamic-variable-bindings dvar)) value))
    
    (defmethod dynamic-variable-bound-p ((dvar standard-dynamic-variable))
      (boundp (dynamic-variable-bindings dvar)))
    
    (defmethod dynamic-variable-makunbound ((dvar standard-dynamic-variable))
      (makunbound (dynamic-variable-bindings dvar)))
    
    (defmethod call-with-dynamic-variable (cont (dvar standard-dynamic-variable)
                                           &optional (val nil val-p))
      (progv (list (dynamic-variable-bindings dvar)) (if val-p (list val) ())
        (funcall cont)))
    

    SURROGATE-DYNAMIC-VARIABLE

    The implementation of the SURROGATE-DYNAMIC-VARIABLE is almost the same as previously. The only difference is that we use the previously defined indirection to safely work with hash tables. Also note, that we are not add the feature condition - both classes is always created.

    (defvar +fake-unbound+ 'unbound)
    (defvar +cell-unbound+ '(no-binding))
    
    (defclass surrogate-dynamic-variable (dynamic-variable)
      ((tls-table
        :initform (make-tls-table)
        :reader dynamic-variable-tls-table)
       (top-value
        :initform +fake-unbound+
        :accessor dynamic-variable-top-value)))
    
    (defmethod dynamic-variable-bindings ((dvar surrogate-dynamic-variable))
      (let ((process (bt:current-thread)))
        (with-tls-table (tls-table (dynamic-variable-tls-table dvar))
          (gethash process tls-table +cell-unbound+))))
    
    (defmethod (setf dynamic-variable-bindings) (value (dvar surrogate-dynamic-variable))
      (let ((process (bt:current-thread)))
        (with-tls-table (tls-table (dynamic-variable-tls-table dvar))
          (setf (gethash process tls-table) value))))
    
    (defun %dynamic-variable-value (dvar)
      (let ((tls-binds (dynamic-variable-bindings dvar)))
        (if (eq tls-binds +cell-unbound+)
            (dynamic-variable-top-value dvar)
            (car tls-binds))))
    
    (defmethod dynamic-variable-value ((dvar surrogate-dynamic-variable))
      (let ((tls-value (%dynamic-variable-value dvar)))
        (when (eq tls-value +fake-unbound+)
          (error 'unbound-variable :name "(unnamed)"))
        tls-value))
    
    (defmethod (setf dynamic-variable-value) (value (dvar surrogate-dynamic-variable))
      (let ((tls-binds (dynamic-variable-bindings dvar)))
        (if (eq tls-binds +cell-unbound+)
            (setf (dynamic-variable-top-value dvar) value)
            (setf (car tls-binds) value))))
    
    (defmethod dynamic-variable-bound-p ((dvar surrogate-dynamic-variable))
      (not (eq +fake-unbound+ (%dynamic-variable-value dvar))))
    
    (defmethod dynamic-variable-makunbound ((dvar surrogate-dynamic-variable))
      (setf (dynamic-variable-value dvar) +fake-unbound+))
    
    
    ;;; Apparently CCL likes to drop^Helide some writes and that corrupts bindings
    ;;; table. Let's ensure that the value is volatile.
    #+ccl (defvar *ccl-ensure-volatile* nil)
    (defmethod call-with-dynamic-variable (cont (dvar surrogate-dynamic-variable)
                                           &optional (val +fake-unbound+))
      (push val (dynamic-variable-bindings dvar))
      (let (#+ccl (*ccl-ensure-volatile* (dynamic-variable-bindings dvar)))
        (unwind-protect (funcall cont)
          (pop (dynamic-variable-bindings dvar)))))
    

    Thread-local variables

    We've refactored the previous code to be extensible. Now we can use metaobjects from the previous post without change. We can also test both implementations in the same process interchangeably by customizing the default class parameter.

    It is the time now to have some fun and extend dynamic variables into variables with top value not shared between different threads. This will enable ultimate thread safety. With our new protocol the implementation is trivial!

    The protocol

    First we will define the protocol class. THREAD-LOCAL-VARIABLE is a variant of a DYNAMIC-VARIABLE with thread-local top values.

    We specify initialization arguments :INITVAL and :INITFUN that will be used to assign the top value of a binding. The difference is that INITVAL specifies a single value, while INITFUN can produce an unique object on each invocation. INITARG takes a precedence over INTIFUN, and if neither is supplied, then a variable is unbound.

    We include the constructor that builds on MAKE-DYNAMIC-VARIABLE-USING-KEY, and macros corresponding to DEFVAR and DEFPARAMETER. Note that they expand to :INITFUN - this assures that the initialization form is re-evaluated for each new thread where the variable is used.

    (defclass thread-local-variable (dynamic-variable) ())
    
    (defmethod initialize-instance :after
        ((self thread-local-variable) &key initfun initval)
      (declare (ignore self initfun initval)))
    
    (defparameter *default-thread-local-variable-class*
      #-fake-progv-kludge 'standard-thread-local-variable
      #+fake-progv-kludge 'surrogate-thread-local-variable)
    
    (defun make-thread-local-variable (&rest initargs)
      (apply #'make-dynamic-variable-using-key
             *default-thread-local-variable-class* initargs))
    
    (defmacro create-tls-variable (&optional (form nil fp) &rest initargs)
      `(make-thread-local-variable 
        ,@(when fp `(:initfun (lambda () ,form)))
        ,@initargs))
    
    (defmacro define-tls-variable (name &rest initform-and-initargs)
      `(defvar ,name (create-tls-variable ,@initform-and-initargs)))
    
    (defmacro define-tls-parameter (name &rest initform-and-initargs)
      `(defparameter ,name (create-tls-variable ,@initform-and-initargs)))
    

    Perhaps it is a good time to introduce a new convention for tls variable names. I think that surrounding names with the minus sign is a nice idea, because it signifies, that it is something less than a global value. For example:

    DYNAMIC-VARS> (define-tls-variable -context- 
                      (progn
                        (print "Initializing context!")
                        (list :context)))
    -CONTEXT-
    DYNAMIC-VARS> -context-
    #<a EU.TURTLEWARE.DYNAMIC-VARS::STANDARD-THREAD-LOCAL-VARIABLE 0x7f7636c08640>
    DYNAMIC-VARS> (dref -context-)
    
    "Initializing context!" 
    (:CONTEXT)
    DYNAMIC-VARS> (dref -context-)
    (:CONTEXT)
    DYNAMIC-VARS> (dset -context- :the-new-value)
    
    :THE-NEW-VALUE
    DYNAMIC-VARS> (dref -context-)
    :THE-NEW-VALUE
    DYNAMIC-VARS> (bt:make-thread
                   (lambda ()
                     (print "Let's read it!")
                     (print (dref -context-))))
    #<process "Anonymous thread" 0x7f7637a26cc0>
    
    "Let's read it!" 
    "Initializing context!" 
    (:CONTEXT) 
    DYNAMIC-VARS> (dref -context-)
    :THE-NEW-VALUE
    

    The implementation

    You might have noticed the inconspicuous operator DYNAMIC-VARIABLE-BINDINGS that is part of the protocol. It returns an opaque object that represents values of the dynamic variable in the current context:

    • for STANDARD-DYNAMIC-VARIABLE it is a symbol
    • for SURROGATE-DYNAMIC-VARIABLE it is a thread-local list of bindings

    In any case all other operators first take this object and then use it to read, write or bind the value. The gist of the tls variables implementation is to always return an object that is local to the thread. To store these objects we will use the tls-table we've defined earlier.

    (defclass thread-local-variable-mixin (dynamic-variable)
      ((tls-table
        :initform (make-tls-table)
        :reader dynamic-variable-tls-table)
       (tls-initfun
        :initarg :initfun
        :initform nil
        :accessor thread-local-variable-initfun)
       (tls-initval
        :initarg :initval
        :initform +fake-unbound+
        :accessor thread-local-variable-initval)))
    

    For the class STANDARD-THREAD-LOCAL-VARIABLE we will simply return a different symbol depending on the thread:

    (defclass standard-thread-local-variable (thread-local-variable-mixin
                                             thread-local-variable
                                             standard-dynamic-variable)
      ())
    
    (defmethod dynamic-variable-bindings ((tvar standard-thread-local-variable))
      (flet ((make-new-tls-bindings ()
               (let ((symbol (gensym))
                     (initval (thread-local-variable-initval tvar))
                     (initfun (thread-local-variable-initfun tvar)))
                 (cond
                   ((not (eq +fake-unbound+ initval))
                    (setf (symbol-value symbol) initval))
                   ((not (null initfun))
                    (setf (symbol-value symbol) (funcall initfun))))
                 symbol)))
        (let ((key (bt:current-thread)))
          (with-tls-table (tls-table (dynamic-variable-tls-table tvar))
            (or (gethash key tls-table)
                (setf (gethash key tls-table)
                      (make-new-tls-bindings)))))))
    

    And for the class SURROGATE-THREAD-LOCAL-VARIABLE the only difference from the SURROGATE-DYNAMIC-VARIABLE implementation is to cons a new list as the initial value (even when it is unbound) to ensure it is not EQ to +CELL-UNBOUND+.

    (defclass surrogate-thread-local-variable (thread-local-variable-mixin
                                              thread-local-variable
                                              surrogate-dynamic-variable)
      ())
    
    (defmethod dynamic-variable-bindings ((tvar surrogate-thread-local-variable))
      (flet ((make-new-tls-bindings ()
               (let ((initval (thread-local-variable-initval tvar))
                     (initfun (thread-local-variable-initfun tvar)))
                 (cond
                   ((not (eq +fake-unbound+ initval))
                    (list initval))
                   ((not (null initfun))
                    (list (funcall initfun)))
                   (t
                    (list +fake-unbound+))))))
        (let ((key (bt:current-thread)))
          (with-tls-table (tls-table (dynamic-variable-tls-table tvar))
            (or (gethash key tls-table)
                (setf (gethash key tls-table)
                      (make-new-tls-bindings)))))))
    

    That's all, now we have two implementations of thread-local variables. Ramifications are similar as with "ordinary" dynamic variables - the standard implementation is not advised for SBCL, because it will crash in LDB.

    Thread-local slots

    First we are going to allow to defined dynamic variable types with an abbreviated names. This will enable us to specify in the slot definition that type, for example (MY-SLOT :DYNAMIC :TLS :INITFORM 34)

    ;;; Examples how to add shorthand type names for the dynamic slots:
    
    (defmethod make-dynamic-variable-using-key ((key (eql :tls)) &rest initargs)
      (apply #'make-dynamic-variable-using-key
             *default-thread-local-variable-class* initargs))
    
    (defmethod make-dynamic-variable-using-key ((key (eql :normal-tls)) &rest initargs)
      (apply #'make-dynamic-variable-using-key
             'standard-thread-local-variable initargs))
    
    (defmethod make-dynamic-variable-using-key ((key (eql :kludge-tls)) &rest initargs)
      (apply #'make-dynamic-variable-using-key
             'surrogate-thread-local-variable initargs))
    
    ;;; For *DEFAULT-DYNAMIC-VARIABLE* specify :DYNAMIC T.
    
    (defmethod make-dynamic-variable-using-key ((key (eql :normal-dyn)) &rest initargs)
      (apply #'make-dynamic-variable-using-key
             'standard-dynamic-variable initargs))
    
    (defmethod make-dynamic-variable-using-key ((key (eql :kludge-dyn)) &rest initargs)
      (apply #'make-dynamic-variable-using-key
             'surrogate-dynamic-variable initargs))
    

    In order to do that, we need to remember he value of the argument :DYNAMIC. We will read it with DYNAMIC-VARIABLE-TYPE and that value will be available in both direct and the effective slot:

    ;;; Slot definitions
    ;;; There is a considerable boilerplate involving customizing slots.
    ;;;
    ;;; - direct slot definition: local to a single defclass form
    ;;;
    ;;; - effective slot definition: combination of all direct slots with the same
    ;;;   name in the class and its superclasses
    ;;;
    (defclass dynamic-direct-slot (mop:standard-direct-slot-definition)
      ((dynamic :initform nil :initarg :dynamic :reader dynamic-variable-type)))
    
    ;;; The metaobject protocol did not specify an elegant way to communicate
    ;;; between the direct slot definition and the effective slot definition.
    ;;; Luckily we have dynamic bindings! :-)
    (defvar *kludge/mop-deficiency/dynamic-variable-type* nil)
    
    ;;; DYNAMIC-EFFECTIVE-SLOT is implemented to return as slot-value values of the
    ;;; dynamic variable that is stored with the instance.
    ;;;
    ;;; It would be nice if we could specify :ALLOCATION :DYNAMIC for the slot, but
    ;;; then STANDARD-INSTANCE-ACCESS would go belly up. We could make a clever
    ;;; workaround, but who cares?
    (defclass dynamic-effective-slot (mop:standard-effective-slot-definition)
      ((dynamic :initform *kludge/mop-deficiency/dynamic-variable-type*
                :reader dynamic-variable-type)))
    

    Moreover we specialize the function MAKE-DYNAMIC-VARIABLE-USING-KEY to the effective slot class. The initargs in this method are meant for the instance. When the dynamic variable is created, we check whether it is a thread-local variable and initialize its INITVAL and INITFUN to values derived from INITARGS, MOP:SLOT-DEFINITION-INITARGS and MOP:SLOT-DEFINITION-INITFUN:

    (defmethod make-dynamic-variable-using-key
        ((key dynamic-effective-slot) &rest initargs)
      (let* ((dvar-type (dynamic-variable-type key))
             (dvar (make-dynamic-variable-using-key dvar-type)))
        (when (typep dvar 'thread-local-variable)
          (loop with slot-initargs = (mop:slot-definition-initargs key)
                for (key val) on initargs by #'cddr
                when (member key slot-initargs) do
                  (setf (thread-local-variable-initval dvar) val))
          (setf (thread-local-variable-initfun dvar)
                (mop:slot-definition-initfunction key)))
        dvar))
    

    The rest of the implementation of DYNAMIC-EFFECTIVE-SLOT is unchanged:

    (defmethod mop:slot-value-using-class
        ((class standard-class)
         object
         (slotd dynamic-effective-slot))
      (dref (slot-dvar object slotd)))
    
    (defmethod (setf mop:slot-value-using-class)
        (new-value
         (class standard-class)
         object
         (slotd dynamic-effective-slot))
      (dset (slot-dvar object slotd) new-value))
    
    (defmethod mop:slot-boundp-using-class
      ((class standard-class)
       object
       (slotd dynamic-effective-slot))
      (dynamic-variable-bound-p (slot-dvar object slotd)))
    
    (defmethod mop:slot-makunbound-using-class
      ((class standard-class)
       object
       (slotd dynamic-effective-slot))
      (dynamic-variable-makunbound (slot-dvar object slotd)))
    

    The implementation of CLASS-WITH-DYNAMIC-SLOTS is also very similar. The first difference in that ALLOCATE-INSTANCE calls MAKE-DYNAMIC-VARIABLE-USING-KEY instead of MAKE-DYNAMIC-VARIABLE and supplies the effective slot definition as the key, and the instance initargs as the remaining arguments. Note that at this point initargs are already validated by MAKE-INSTANCE. The second difference is that MOP:COMPUTE-EFFECTIVE-SLOT-DEFINITION binds the flag *KLUDGE/MOP-DEFICIENCY/DYNAMIC-VARIABLE-TYPE* to DYNAMIC-VARIABLE-TYPE.

    ;;; This is a metaclass that allows defining dynamic slots that are bound with
    ;;; the operator SLOT-DLET, and, depending on the type, may have thread-local
    ;;; top value.
    ;;;
    ;;; The metaclass CLASS-WITH-DYNAMIC-SLOTS specifies alternative effective slot
    ;;; definitions for slots with an initarg :dynamic.
    (defclass class-with-dynamic-slots (standard-class) ())
    
    ;;; Class with dynamic slots may be subclasses of the standard class.
    (defmethod mop:validate-superclass ((class class-with-dynamic-slots)
                                        (super standard-class))
      t)
    
    ;;; When allocating the instance we initialize all slots to a fresh symbol that
    ;;; represents the dynamic variable.
    (defmethod allocate-instance ((class class-with-dynamic-slots) &rest initargs)
      (let ((object (call-next-method)))
        (loop for slotd in (mop:class-slots class)
              when (typep slotd 'dynamic-effective-slot) do
                (setf (mop:standard-instance-access
                       object
                       (mop:slot-definition-location slotd))
                      (apply #'make-dynamic-variable-using-key slotd initargs)))
        object))
    
    ;;; To improve potential composability of CLASS-WITH-DYNAMIC-SLOTS with other
    ;;; metaclasses we treat specially only slots that has :DYNAMIC in initargs,
    ;;; otherwise we call the next method.
    (defmethod mop:direct-slot-definition-class
        ((class class-with-dynamic-slots) &rest initargs)
      (loop for (key) on initargs by #'cddr
            when (eq key :dynamic)
              do (return-from mop:direct-slot-definition-class
                   (find-class 'dynamic-direct-slot)))
      (call-next-method))
    
    (defmethod mop:compute-effective-slot-definition
        ((class class-with-dynamic-slots)
         name
         direct-slotds)
      (declare (ignore name))
      (let ((latest-slotd (first direct-slotds)))
        (if (typep latest-slotd 'dynamic-direct-slot)
            (let ((*kludge/mop-deficiency/dynamic-variable-type*
                    (dynamic-variable-type latest-slotd)))
              (call-next-method))
            (call-next-method))))
    
    (defmethod mop:effective-slot-definition-class
        ((class class-with-dynamic-slots) &rest initargs)
      (declare (ignore initargs))
      (if *kludge/mop-deficiency/dynamic-variable-type*
          (find-class 'dynamic-effective-slot)
          (call-next-method)))
    

    Finally the implementation of SLOT-DLET does not change:

    ;;; Accessing and binding symbols behind the slot. We don't use SLOT-VALUE,
    ;;; because it will return the _value_ of the dynamic variable, and not the
    ;;; variable itself.
    (defun slot-dvar (object slotd)
      (check-type slotd dynamic-effective-slot)
      (mop:standard-instance-access
       object (mop:slot-definition-location slotd)))
    
    (defun slot-dvar* (object slot-name)
      (let* ((class (class-of object))
             (slotd (find slot-name (mop:class-slots class)
                          :key #'mop:slot-definition-name)))
        (slot-dvar object slotd)))
    
    (defmacro slot-dlet (bindings &body body)
      `(dlet ,(loop for ((object slot-name) val) in bindings
                    collect `((slot-dvar* ,object ,slot-name) ,val))
         ,@body))
    

    Finally we can define a class with slots that do not share the top value:

    DYNAMIC-VARS> (defclass c1 ()
                      ((slot1 :initarg :slot1 :dynamic nil :accessor slot1)
                       (slot2 :initarg :slot2 :dynamic t   :accessor slot2)
                       (slot3 :initarg :slot3 :dynamic :tls :accessor slot3))
                      (:metaclass class-with-dynamic-slots))
    #<The EU.TURTLEWARE.DYNAMIC-VARS::CLASS-WITH-DYNAMIC-SLOTS EU.TURTLEWARE.DYNAMIC-VARS::C1>
    DYNAMIC-VARS> (with-slots (slot1 slot2 slot3) *object*
                    (setf slot1 :x slot2 :y slot3 :z)
                    (list slot1 slot2 slot3))
    (:X :Y :Z)
    DYNAMIC-VARS> (bt:make-thread
                   (lambda ()
                     (with-slots (slot1 slot2 slot3) *object*
                       (setf slot1 :i slot2 :j slot3 :k)
                       (print (list slot1 slot2 slot3)))))
    
    #<process "Anonymous thread" 0x7f76424c0240>
    
    (:I :J :K) 
    DYNAMIC-VARS> (with-slots (slot1 slot2 slot3) *object*
                    (list slot1 slot2 slot3))
    (:I :J :Z)
    

    What can we use it for?

    Now that we know how to define thread-local variables, we are left with a question what can we use it for. Consider having a line-buffering stream. One possible implementation could be sketched as:

    (defclass line-buffering-stream (fancy-stream)
      ((current-line :initform (make-adjustable-string)
                     :accessor current-line)
       (current-ink :initform +black+
                    :accessor current-ink)))
    
    (defmethod stream-write-char ((stream line-buffering-stream) char)
      (if (char= char #
    ewline)
          (terpri stream)
          (vector-push-extend char (current-line stream))))
    
    (defmethod stream-terpri ((stream line-buffering-stream))
      (%put-line-on-screen (current-line stream) (current-ink stream))
      (setf (fill-pointer (current-line stream)) 0))
    

    If this stream is shared between multiple threads, then even if individual operations and %PUT-LINE-ON-SCREEN are thread-safe , we have a problem. For example FORMAT writes are not usually atomic and individual lines are easily corrupted. If we use custom colors, these are also a subject of race conditions. The solution is as easy as making both slots thread-local. In that case the buffered line is private to each thread and it is put on the screen atomically:

    (defclass line-buffering-stream (fancy-stream)
      ((current-line
        :initform (make-adjustable-string)
        :accessor current-line
        :dynamic :tls)
       (current-ink
        :initform +black+
        :accessor current-ink
        :dynamic :tls))
      (:metaclass class-with-dynamic-slots))
    

    Technique is not limited to streams. It may benefit thread-safe drawing, request processing, resource management and more. By subclassing DYNAMIC-VARIABLE we could create also variables that are local to different objects than processes.

    I hope that you've enjoyed reading this post as much as I had writing it. If you are interested in a full standalone implementation, with tests and system definitions, you may get it here. Cheers!








    ic

    bro cmon america

    Today on Married To The Sea: bro cmon america


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




    ic

    hold up american fla

    Today on Married To The Sea: hold up american fla


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




    ic

    second notice

    Today on Married To The Sea: second notice


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




    ic

    america must outlaw children

    Today on Married To The Sea: america must outlaw children


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




    ic

    marbles magic

    Today on Married To The Sea: marbles magic


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




    ic

    our stock price

    Today on Married To The Sea: our stock price


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




    ic

    rich people are smarter

    Today on Married To The Sea: rich people are smarter


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!




    ic

    welcome to the magical lair

    Today on Married To The Sea: welcome to the magical lair


    This RSS feed is brought to you by Drew and Natalie's podcast Garbage Brain University. Our new series Everything Is Real explores the world of cryptids, aliens, quantum physics, the occult, and more. If you use this RSS feed, please consider supporting us by becoming a patron. Patronage includes membership to our private Discord server and other bonus material non-patrons never see!