Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
164514 stories
·
33 followers

2.6 Experimental (2.6.6-experimental) 🧪

1 Share

Windows App SDK 2.6 Experimental extends the experimental controls and resource APIs, with more accessible WinUI interactions and new video-scaling options. It follows Windows App SDK 2.6.5 stable and brings forward the changes from that release alongside the experimental-only additions below.

What's new in WinAppSDK 2.6 Experimental:

  • TableView hierarchy and live data. TableViewSource.ParentBy builds hierarchical rows, and IsLiveShaping keeps sorting, filtering, grouping, and hierarchy up to date as item properties change. The control also adds cell and column-header context menus, row expansion APIs and automation support, and improvements to high-contrast rendering and column resizing.
  • Chart accessibility. ChartAutomationPeer and chart parts add UI Automation support to the experimental Chart control.
  • Zero-copy resource access. ResourceCandidate.ValueAsMemoryBuffer provides access to embedded resource bytes without copying them. New native resource-view APIs bring the same capability to C and C++ callers.
  • Scrolling content outside bounds. ScrollView.CanContentRenderOutsideBounds and ScrollPresenter.CanContentRenderOutsideBounds let apps opt out of viewport clipping so content can render outside the bounds. Both properties default to false.
  • Video-scaling options. A new VideoScaler.CreateAsync overload accepts execution-device, scenario, and performance or quality preferences through VideoScalerCreationOptions. Input color-matrix and color-range settings let apps describe BT.601 or BT.709 frames with full or limited color ranges.

Bugs fixed:

Bug fix Runtime Compatibility Change
Improved Window.Width and Window.Height behavior after a window closes, retaining the last restored client size. Explicitly set height is preserved when title-bar settings change on a restored window, and native size requests are limited to 65,535 physical pixels per dimension. WinUI_WindowWidthHeight
Added UI Automation selection support for InkToolbar flyout items and a stroke-width slider automation peer so Narrator announces stroke widths instead of percentages. Fixed InkCanvas input clipping at display scaling above 100%. InkCanvas_InkingSupport
Fixed custom dependency properties and metadata being lost when shutting down and restarting XAML. WinUI_EnableShutdownRestart
Added clearer debugger guidance when XamlChartsResources is missing. Charts_ExperimentalControlApi
Fixed ContentDialog entrance animations and unwanted keyboard-accelerator tooltips on MenuFlyout items when XamlChangeId.OptimizeApplyStyles is enabled. See microsoft/microsoft-ui-xaml#11257. Xaml_OptimizeApplyStylesApplyTemplate
Fixed a reference cycle during input teardown by closing the system InputSite. LiftedInput_CloseSystemInputSite
Fixed a crash when a Flyout theme changes before its presenter is available. See microsoft/microsoft-ui-xaml#12089. WinUI_FlyoutCheckForNullPresenter
Fixed missing InkToolbar translations for accessible names, color descriptions, and pen configuration text. See microsoft/microsoft-ui-xaml#12162. N/A, shared localization-resource update

To see everything that's new and changed, see the full Windows App SDK 2.6 Experimental release notes.

Try it out

Getting started

To get started using Windows App SDK to develop Windows apps, check out the following documentation:


This discussion was created from the release 2.6 Experimental (2.6.6-experimental) 🧪.
Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

OpenAI valued at $1.25 trillion, as rival Anthropic nears its IPO

1 Share

OpenAI is now valued at $1.25 trillion in a new round of funding that has raised another $11 billion, according to Bloomberg. That would make it one of the world's most valuable companies, not long after raising $122 billion from investors in March at a valuation of $852 billion.

Echoing FT and Wall Street Journal reports from last week, Bloomberg says OpenAI is still pursuing additional investments that could increase the amount to $20 billion, and that it had initially hoped to raise as much as $30 billion at a higher valuation of $1.4 trillion. That was before tech stocks dropped, and the results of the midterms showed how much public o …

Read the full story at The Verge.

Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Beyond code generation: A developer’s guide to agentic engineering

1 Share

This October, GitHub saw more than 1.2 billion commits in one week—over 400% more than a year earlier. This is more commits than GitHub received in all of 2025. Chances are, you’re generating more code than you were a year ago. But ask yourself: do you feel 400% more productive than you were last year?

Building software takes more than a good prompt. As AI speeds up implementation, software developers are feeling more pressure in other places: a Faros AI study found that pull requests spent 441% longer in review when teams went from low to high AI adoption.

As a software developer, you gather requirements and steer implementation, review changes and evaluate test results, then investigate what happens in production. Agents can help you work more efficiently across all of these stages: Plan, Build, Verify, and Observe. We’ll walk through what an effective agentic engineering workflow looks like on GitHub.

Plan: Get everyone and every agent on the same page

See if this sounds familiar. You’re planning an API change. The design is in one tool, a compatibility requirement is buried in Slack, and an old pull request explains why the current implementation works this way. Before you can write a useful plan, you need to connect those details, compare approaches, and agree on how existing clients will keep working. What looked like a straightforward planning task has become hours of searching, cross-checking, and chasing answers. You’re exhausted before implementation even begins.

You can use Copilot to pull that scattered context into one place, work through the tradeoffs, and turn the decisions into an implementation plan.

  • Plan together where your team already works. Use Copilot in Slack or Teams to collaborate with everyone involved—from product managers and designers to analysts and business stakeholders. Bring in context from connected tools so your team can check assumptions, contribute ideas, and refine requirements together.
  • Turn agreed-upon requirements into actionable work. Ask Copilot to create GitHub Issues with the context and acceptance criteria needed for implementation, breaking out tasks that can be handled separately.
  • Keep routine triage from consuming planning time. Use GitHub Agentic Workflows to label your issues and estimate task complexity. Assign low-complexity tasks directly to Copilot, then spend your time focusing on harder decisions.
  • Give complex work the planning it needs. Use Plan Mode in your preferred Copilot surface to compare approaches and refine an implementation plan. Review and approve the plan before handing it off to Copilot to begin implementation.

What took days of searching and context gathering now produces a reviewable plan, explicit acceptance criteria, and scoped issues, with your time spent on the decisions instead of the retrieval. Then, once you settle on an approach, you’ve already set yourself and your agents up to implement your plan.

Build: Move more than one task forward at a time

You’re ready to turn that plan into code. But life has other ideas. Your agent is halfway through a task, your next meeting starts in five minutes, and your dog has been staring at the door for twenty. Then someone in the meeting points out an edge case the implementation needs to handle. Everyone needs something from you at once. You’d like to keep making progress, but you can’t be in three places at the same time.

You can keep your work moving without being tied to the computer where you started it.

  • Continue your conversation from another device. Session continuity lets you pick up the same Copilot session across clients, so you can work wherever it’s most convenient for you.
  • Let work continue after you close your laptop. Copilot runs tasks in cloud sandboxes, keeping execution off your machine and allowing you to leave and return to a session later.
  • Work on separate tasks without mixing their edits. The Copilot app gives sessions isolated copies of the repository, so an implementation task and a documentation update don’t share an unfinished working tree.
  • Carry project knowledge into each task. Repository instructions capture conventions and validation commands, while shared memory stores useful facts Copilot can reuse across a repository. Copilot Dreaming reviews eligible sessions and pull request feedback to keep memories current and propose instruction or skill updates. That means less time re-explaining how your team works and more consistent help from Copilot across tasks.
  • Match the models to the work. Select Copilot Fusion to choose models and reasoning depth for the task, bringing in additional models to review a solution when needed. You set the effort level to balance quality, cost, and response time.
  • Follow company rules wherever you work. Enterprise managed settings carry your company’s policies and configured tools across Copilot surfaces, reducing setup differences as you move between clients.

Writing code is more than shooting off a single prompt to your LLM and seeing what you get back. And with more work on your plate than ever, it can be difficult to maintain your coding standards and deliver your projects on time. Copilot helps tasks progress independently with shared project knowledge, while letting you pick up a session where you left off across devices. That gives you more room to work through implementation decisions and handle changes as they come up. And as your work finishes, you have focused changes ready to review.

Verify: Spend your attention where it counts

After spending an hour wrangling your agents to get them moving in the right direction, you think you’ve all landed in a good spot. So you open a pull request labeled “small refactor.” It changes 47 files. You wanted feedback on shared validation logic, but it’s buried among updates to every endpoint that calls it, formatting changes, and several hundred lines of generated tests. You wonder how long it will take to get an approval on this, given the endless list of pull requests on your team’s review board.

That review queue is the 441% in practice. You can shorten the wait from both ends: make your changes easier for a person to review, and let Copilot clear the routine checks and fixes before anyone opens the diff.

  • Stack up smaller, more focused pull requests instead of creating a single, massive change. You and your agents can use stacked pull requests to separate dependent work into smaller pieces. This lets each PR be reviewed on its own, keeps dependencies visible, and unblocks teams by letting them build on top of your work before it merges.
  • Catch routine quality issues before they consume review time. GitHub Code Quality checks changes against coding practices and your organization’s standards, catching issues before they reach a human reviewer and saving time for both authors and reviewers.
  • Get review help matched to the change. Copilot code review gives automated feedback, with lighter analysis for simple changes and deeper analysis for complex ones. You can set that depth yourself or let Copilot select it for each pull request.
  • Check security across more of your codebase. AI Scan finds vulnerabilities in languages and files beyond CodeQL’s coverage. AI secret detection helps identify unstructured secrets, such as passwords, that pattern matching can miss.
  • Keep new dependencies within policy. License compliance checks dependency licenses against your company’s rules before merging. GitHub package firewall, currently in public preview for npm, can block downloads that violate configured security policies.
  • Resolve findings and follow-up work without babysitting every pull request. Agentic Autofix proposes repairs for security issues, while Agent Merge automatically addresses failing checks, review comments, and merge conflicts—helping you keep your pull requests moving and preparing them for merge. And, if you configure it, Copilot will even merge your pull requests once your verification steps are passing.

Smaller changes with the routine work already handled means fewer rounds of back-and-forth. Your reviewers get to spend their attention on whether the code does what you intended, and what could go wrong.

Observe: Learn from what happens after you ship

Your change gets approved, the deployment checks pass, and you finally get to cross it off your list. The next morning, your on-call dashboard shows intermittent timeouts, and support reports that a few large customers are affected. Now you’re comparing logs, release history, and customer reports while someone asks whether you should roll back. “All checks passed” feels less reassuring than it did yesterday.

Bring operational evidence and engineering context together so you can decide what to do.

  • Check assumptions against real behavior. With access to your observability tools and business data through connectors such as Fabric, Copilot can help investigate which requests or customer groups are affected.
  • Capture the finding where someone can act on it. Ask Copilot to draft an issue with supporting evidence and acceptance criteria, including a regression test for the failure you uncovered.
  • Carry useful lessons into future work. Review suggested improvements from Dreaming, which uses sessions and repository activity to help refine memories, skills, and code review guidance.

Figuring out what happened after a release can leave you bouncing between dashboards, conversations, and commit history. With Copilot helping connect that information, you can document what you found, create follow-up issues, and improve the guidance available for future tasks. You spend more time deciding what needs to change and less time retracing the investigation. And your next plan starts with what you learned from the software people are actually using.

Where to start

Take one piece of work already on your plate and look beyond the coding task. What context does it need? How will you review and test it? How will you know it worked after it ships?

You don’t have to change your whole process at once. Start with something that frustrates you most this week and put an agent on it. Then expand to other parts of your process over time. Once agents are helping across your whole workflow, you might finally feel as productive as your commit count says you are.

We covered this topic live at GitHub Universe 2026 in “Agentic Engineering: Past the Pilot.”

Get started with Copilot >

The post Beyond code generation: A developer’s guide to agentic engineering appeared first on The GitHub Blog.

Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete

AI Stockpocalypse, Anthropic Nears RSI?, Prediction Markets vs. Pollsters

1 Share

Ranjan Roy from Margins is back for our weekly discussion of the latest tech news. We cover: 1) The midterms impact on AI 2) Why the market struggles to trade AI in a normal way 3) Will the AI vision ever be realized as planned after this week? 4) A simple theory of the case on the AI trade 5) If datacenters are banned, do AI labs make tradeoffs in training, inference, or safety? 6) Will Anthropic's IPO still go on as planned? 7) Can the AI labs help turn around datacenter unpopularity 8) Is Anthropic getting close to RSI? 9) Who did better: The polls or prediction markets 10) An homage to Alex's special, temporary red chair

---

Enjoying Big Technology Podcast? Please rate us five stars ⭐⭐⭐⭐⭐ in your podcast app of choice.

Want a discount for Big Technology on Substack + Discord? Here’s 25% off for the first year: https://www.bigtechnology.com/subscribe?coupon=0843016b

Learn more about your ad choices. Visit megaphone.fm/adchoices





Download audio: https://pdst.fm/e/tracking.swap.fm/track/t7yC0rGPUqahTF4et8YD/pscrb.fm/rss/p/traffic.megaphone.fm/AMPP6817214496.mp3
Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Outbox Pattern in Postgres: Concurrent Workers vs Reading the WAL

1 Comment and 2 Shares

The outbox pattern saves a message in the same database transaction as the data change it describes, then uses a background worker to send it. Concurrent workers can drain the outbox table with FOR UPDATE SKIP LOCKED, but new messages wait for the next poll. Reading the Postgres write-ahead log with logical replication delivers them in about a millisecond at low load, at the cost of a replication slot and a cleanup job.

If you build distributed systems, you probably heard about the Outbox pattern.

Your API saves an order, then crashes before it can send the message announcing that order.

The order is in the database, but other services waiting for that message never hear about it. The outbox pattern handles this by saving the message in a separate table, in the same database transaction as the order.

Either both are saved or neither is. This is the A in ACID. Atomicity.

A background worker reads the saved messages and publishes them to a message broker for other services to receive. If the API crashes after saving the order, the message is still there for the worker to send. Querying the table repeatedly for pending messages is called polling.

Polling is a good starting point for an outbox processor. It's decently fast and easy to implement. But the delay between checks is a tradeoff. Checking more often means more database queries, while waiting longer delays new messages.

I want to reduce that wait by having Postgres tell me when there's something to process, and see how that compares with the polling workers.

Concurrent Workers Draining the Table

For this walkthrough, we'll use an order API on Postgres that saves each OrderCreated message in an outbox_messages table, with RabbitMQ as the broker.

I want to run several workers at once, and Postgres row locks stop two of them from grabbing the same message.

Each worker starts a transaction and claims a batch with this query, using Dapper:

SELECT id, type, content
FROM outbox_messages
WHERE processed_on_utc IS NULL
ORDER BY occurred_on_utc
LIMIT 1000
FOR UPDATE SKIP LOCKED

FOR UPDATE locks the rows this worker picked until its transaction ends. SKIP LOCKED makes the next worker take other rows instead of waiting. The locks live in Postgres, so these workers can run in one process or in several.

The worker keeps that transaction open while it publishes with RabbitMQ.Client, then sets processed_on_utc and commits once RabbitMQ confirms.

When a worker runs out of rows, it sleeps for one second with Task.Delay and tries again.

The API writes an order and its message in one transaction. Workers poll and publish to RabbitMQ.

If a worker crashes before it commits, those rows are still unprocessed, and a worker picks them up again. Some of those messages may have already reached RabbitMQ, so the same message can arrive twice.

That makes this at-least-once delivery, so your consumers must be idempotent.

Why Not Just LISTEN/NOTIFY?

Postgres has a built-in way to wake those workers. With LISTEN/NOTIFY, one connection runs LISTEN outbox_new, and Postgres tells it when another connection runs NOTIFY outbox_new and commits.

My first implementation sent NOTIFY from a database trigger on the outbox table. It needs no API changes and worked well at 100 orders per second. But with 100 concurrent users, throughput fell from 8,070 orders per second with polling to 256.

The trigger runs inside the order transaction. Before that transaction commits, Postgres takes a lock and holds it until after the commit. Every transaction that sends a notification takes the same lock.

A commit normally waits for a disk flush, and concurrent transactions can share one, which is called group commit. With the lock, each order waits for the one in front of it to finish its own flush.

Without NOTIFY, commits share one disk write. With it, each waits for the lock.

Sending NOTIFY after the commit, from a separate connection, avoids that lock. At 100 users the API handled 7,674 orders per second, about 5% below polling, and at 100 orders per second a message took 1.91 ms from commit to consumer.

But now I'm running a notifier, a listener, and a fallback poll for notifications sent while the listener is disconnected, all to wake the same workers. Postgres already has a record of every new row, so let's read that instead.

Read the Outbox From the WAL

Postgres writes every change to the write-ahead log (WAL). Logical replication streams committed changes to my app, so there's no polling and no wake-up to miss.

The WAL reader follows the log, publishes to RabbitMQ, and confirms its position.

You need wal_level=logical, which takes a restart on Postgres 18. Then create a publication and a replication slot:

CREATE PUBLICATION outbox_pub FOR TABLE outbox_messages WITH (publish = 'insert');
SELECT pg_create_logical_replication_slot('outbox_slot', 'pgoutput');

The slot remembers how far my reader has confirmed, so it can resume after a restart.

Let's read the stream with an Npgsql replication connection:

await using var connection = new LogicalReplicationConnection(connectionString);
await connection.Open(cancellationToken);

var slot = new PgOutputReplicationSlot("outbox_slot");
var options = new PgOutputReplicationOptions("outbox_pub", PgOutputProtocolVersion.V1);

await foreach (var message in connection.StartReplication(slot, options, cancellationToken))
{
    if (message is InsertMessage insert)
    {
        await PublishAsync(insert, cancellationToken);
    }
    else if (message is CommitMessage commit)
    {
        connection.SetReplicationStatus(commit.TransactionEndLsn);
    }
}

PublishAsync publishes to RabbitMQ and waits for the confirm. I only confirm a position after that, when the CommitMessage arrives, so a crash replays messages instead of losing them.

This loop only suits low rates, because waiting on every message drained about 1,600 per second. So the reader I measured publishes first and waits for confirms, in order, in a separate task.

While the reader is down, the slot keeps all the server's WAL on disk, so a long outage can fill it. You can cap that with max_slot_wal_keep_size, but Postgres invalidates a slot past the cap.

This reader is a prototype, and I haven't tested it through a failover or on managed Postgres.

Workers vs the WAL Reader

The main benchmark comes from my scaling article: I seeded a backlog, started the processor, and counted what RabbitMQ had after one minute, in messages per second (MPS). To keep it fair, the WAL reader also marks rows as processed in batches, so it does the same UPDATE as the workers. This ran on one machine, with Postgres 18 and RabbitMQ 4 in Docker:

SetupMPS after 1 minuteAverage commit to consumer, 100 orders/sAverage commit to consumer, 100 users
1 worker14,516 to 15,050500 ms8.5 s
5 workers22,216 to 24,133419 ms54 to 85 ms
WAL reader27,962 to 34,9300.83 ms8.6 ms

One worker already moves about 15,000 messages per second, and five get to about 23,000. The WAL reader leads at about 31,000, and it reached about 57,600 without the UPDATE. Both move thousands of messages per second, so throughput alone isn't a reason to switch.

Latency is the real difference. New messages wait for a worker's one-second sleep, so five workers average 419 ms at 100 orders per second, while the reader stays under a millisecond. At 100 users, one worker fell 8.5 seconds behind, five workers averaged 54 to 85 ms, and the reader held 8.6 ms.

A crash goes the other way. A hard kill partway through 200,000 rows lost nothing, but the workers sent 4,536 duplicates and the WAL reader 31,095, because it replays from its last confirmed position.

Workers also scale out, because you can add more in any process. A slot has one reader, which keeps commit order, but I can't scale it out.

Summary

Keep polling if the delay is acceptable for your application. Add workers when you need more throughput, because the row locks already handle the concurrency.

Read the WAL if you need new messages right away and can run a replication slot and a cleanup job. If you use NOTIFY instead, send it after the commit and keep a fallback poll.

A worker can still crash after publishing but before committing its processed markers, causing a message to be sent again. Consumers must be idempotent: processing the same message twice must not repeat its effects.

Thanks for reading.

And stay awesome!




Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete
1 public comment
axizhiwei
4 hours ago
reply
OEM custom caps for trade news China factory

Catch the import. Name the culprit. Keep threads free: Introducing gilguard

1 Share

Catch the import. Name the culprit. Keep threads free: gilguard

TL;DR: gilguard is a tiny, zero-dependency package for free-threaded Python (3.13t, 3.14t, 3.15t). Import it first. If any later import quietly turns the GIL back on, you get an error naming the extension module, the package that shipped it, and the line of your code that pulled it in. python -m gilguard audits an entire environment in one go. It’s on PyPI now, and it’s the second piece of a larger free-threaded Python effort I’ve been working on.

The quiet failure mode of free-threaded Python

Free-threaded Python is officially supported as of 3.14, and I’m all in. Threads that actually run Python code in parallel? Yes please. But the migration has a failure mode that I think is going to bite a lot of teams, and there isn’t much written about it.

Here’s the scenario. You move a service to 3.14t. You give the hot path a thread pool. You deploy, pull up the dashboard, and the throughput graph does… nothing. It’s the exact same shape as last week, as if the whole migration had been a dream.

An hour later you find the reason, one line in a startup log that scrolled past in half a second:

RuntimeWarning: The global interpreter lock (GIL) has 
been enabled to load module 'somepackage._speedups', 
which has not declared that it can run safely without the GIL.

That line is CPython being careful. Every C extension has to declare that it’s safe to run without the GIL (via the Py_mod_gil slot). When the interpreter loads one that hasn’t, it turns the GIL back on for the rest of the process and carries on. Your threads still run, they just take turns again. And here’s the kicker: once the GIL is on, nothing can turn it back off for the life of that process.

The warning names a private module you’ve never heard of. It doesn’t tell you which package you installed shipped it, or which of your imports dragged it in. So now you’re bisecting imports.

What is gilguard?

gilguard turns that one line into an exception, raised at the import statement that caused it. Make it the first import in your program and you’re done. You don’t call anything or configure anything:

import gilguard  # must be first

import fastapi
import pandas  # <- raises here if anything in pandas' import tree needs the GIL

When something in that tree forces the GIL back on, this is what you see instead of a warning you’d miss:

gilguard.GILEnabledError: Importing 'gilneeder._speedups' re-enabled the GIL.

  Module:       gilneeder._speedups
  File:         .../site-packages/gilneeder/_speedups.cpython-314t-x86_64-linux-gnu.so
  Distribution: gilneeder 1.2.0
  Import chain: gilneeder -> gilneeder._speedups
  Your code:    /home/me/app.py:5
  Imported by:  .../site-packages/gilneeder/__init__.py:1

This extension module does not declare free-threading 
support (Py_mod_gil), so CPython turned the GIL back 
on for the rest of the process. It cannot be turned off again.

Options:
  - Upgrade gilneeder to a release that ships free-threaded (cp314t) wheels.
  - Drop or replace gilneeder.
  - If you have verified it is thread-safe, run with PYTHON_GIL=0 to keep the GIL off (at your own risk).
  - Run `python -m gilguard` to list every installed package that does this.

(gilneeder is one of gilguard’s test fixtures, a tiny C extension built to misbehave so the real ones don’t have to.)

Everything you’d otherwise be asking a debugger for is right there: which module flipped it, what file it lives in, and which distribution and version shipped it (so you know what to upgrade or report). You also get the import chain from your code down to the culprit, which matters when the offender is a dependency of a dependency of something you installed for one function, and the line in your code where it all started.

You might notice that GILEnabledError derives from BaseException rather than Exception, putting it alongside KeyboardInterrupt. Here’s why. Lots of packages have an optional speedup import like try: import fastthing / except ImportError: import slowthing. If gilguard raised a normal exception, that except would eat it, the package would quietly fall back to its pure-Python path, and your app would carry on with the GIL already back on. The report would end up inside someone else’s except block, where nobody would ever see it. With BaseException, it sails right past those handlers. To catch it, you have to ask for GILEnabledError by name (or use a bare except:, and you know better).

Not ready to fail hard? Set GILGUARD_ACTION=warn and you get the same report as a warning while the program keeps running. Set it to off and the same code runs on a standard build.

How do I find every offender in an environment?

A single process can only tell you about the first module that flips the GIL. After that it’s already on, and the rest have nothing left to flip. So gilguard also ships a scanner:

python -m gilguard
GIL    gilneeder                via gilneeder._speedups  [gilneeder 1.2.0]

8 checked: 7 ok, 1 re-enable the GIL, 0 failed to import.

It gives every installed package its own fresh interpreter (in parallel, across your CPUs), so none of them can hide behind the one that went first. It exits 1 when it finds anything, so it drops into CI as a single step without any extra scripting. There’s --json for tooling and --strict if a package that fails to import should fail the build too. Pass package names to check just those, which is how you re-check one after an upgrade to see if the fix took.

The scan has one blind spot: extensions a package loads lazily, on first use of a feature, don’t show up at import time. For those, run your test suite with gilguard loaded as a plugin:

pytest -p gilguard

pytest loads -p plugins before it touches conftest.py or any test module, so the guard is watching before your code imports anything. The two checks cover different gaps. The scanner sees every installed package but only at import time, and the test suite sees lazy loads but only on the code paths your tests run. Use both. Tests and CI has a GitHub Actions job that does exactly that.

What does a clean run actually mean?

“The GIL stayed off” is easy to over-read. Here are the limits. A clean run means none of the imports that actually ran turned the GIL back on. It does not mean your code or your dependencies are thread-safe, and it does not mean your program gets faster with more threads. Those are separate problems with their own tests. gilguard catches one specific failure, and that’s all it tries to do.

Once you have a name, the docs have a whole page on what to do with it: upgrade, report upstream (with exactly which lines to paste into the issue), replace, or, if the extension is yours, the one slot that declares support, with the one-line equivalents for Cython, pybind11, and PyO3.

Why I built this

When I announced chameleon-rs ten days ago, I said free-threaded Python means we need to start expecting more from our web apps. What I didn’t say is that chameleon-rs was the first piece of a larger free-threaded effort, and just the first one I am able to talk about publicly. gilguard is the second.

It’s a much smaller piece, a few hundred lines of plain Python with no dependencies. But it’s the kind of building block everything else depends on. There’s no point making a template engine or a web framework thread-safe and fast if one dependency turned the lock back on at startup and nobody noticed for a month. Before you can make free-threaded Python fast, you have to be able to trust that you’re actually running free-threaded.

My goal with all of this work is simple to state: free-threaded Python faster, safer, better, and adopted sooner. gilguard is my contribution to the “safer” and “sooner” parts. I’ll write about the rest as soon as I’m able to.

Try it

uv pip install gilguard

Then import gilguard at the top of your entry point, or run python -m gilguard against your current environment and see what’s lurking. I’d love to hear what it finds. The docs are short and the code is on GitHub. If you use an AI coding agent, the docs also ship an agent skill that teaches it the rules (first import, don’t catch the error, what a pass means), so it can wire gilguard into your project and CI without you explaining any of this.

Read the whole story
alvinashcraft
7 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories