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

Dave Thackeray on Critical Thinking and Staying Human with AI

1 Share
Dave Thackeray talks authenticity, critical thinking, failure, and purpose, and why he hopes AI can help us embrace our own humanity.
Read the whole story
alvinashcraft
2 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

The timeout that never fired

1 Share

We have the following code:

public async Task<Result<TData>> SendAsync<TData>(
    Func<HttpRequestMessage> requestFactory,
    RequestOptions? requestOptions = null,
    CancellationToken cancellationToken = default)
{
    requestOptions ??= new RequestOptions();
    var policy = requestOptions.Policy ?? DefaultPollyPolicy;
    var result = await policy.ExecuteAndCaptureAsync(async () =>
    {
        using var request = requestFactory();

        if (cancellationToken == CancellationToken.None)
        {
            // Default timeout 5 seconds
            using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
            cancellationToken = cancellationTokenSource.Token;
        }

        using var response =
            await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
        ................
    });
    ................
}

It sends a request using HttpClient, wrapped in a Polly policy that, by default, retries once. If the caller doesn't pass a CancellationToken, we create our own with a default timeout of 5 seconds.

Looks reasonable, right? Can you spot the bug? There are actually two of them :)

Bug 1 - the timeout never fires

A using var is disposed when its enclosing scope ends. Here, the enclosing scope is the if block, not the lambda. So the CancellationTokenSource is disposed at the closing brace, before the request is even sent.

When a CancellationTokenSource is disposed, its timer is disposed as well. The token we got from it still "works", nothing throws, it will just never be cancelled.

Small repro:

var cancellationToken = CancellationToken.None;
var stopwatch = Stopwatch.StartNew();
if (cancellationToken == CancellationToken.None)
{
    using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(1));
    cancellationToken = cancellationTokenSource.Token;
}

try
{
    await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
    Console.WriteLine($"Completed after {stopwatch.Elapsed.TotalSeconds:F1}s");
}
catch (Exception e)
{
    Console.WriteLine($"{e.GetType().Name} after {stopwatch.Elapsed.TotalSeconds:F1}s");
}
Completed after 3.0s

The timeout is 1 second, but the delay runs for the full 3 seconds.

So in the real code, the only timeout left is HttpClient.Timeout, and the default for that is 100 seconds.

Bug 2 - the retry reuses the token

cancellationToken is a parameter of the method, and the lambda captures it. So when the lambda assigns it, it's not a local copy that changes, it's the same variable for every invocation of the lambda.

When Polly retries, cancellationToken == CancellationToken.None is no longer true. The retry skips the if block and reuses the token from the first attempt, the one that belongs to an already disposed CancellationTokenSource.

This also means that just moving the using var out of the if block isn't enough. That would fix the first attempt, but the retry would still run without a timeout.

The fix

using var timeoutCts = cancellationToken == CancellationToken.None
    // Default timeout 5 seconds
    ? new CancellationTokenSource(TimeSpan.FromSeconds(5), _timeProvider)
    : null;
var token = timeoutCts?.Token ?? cancellationToken;

using var response =
    await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);

The CancellationTokenSource now lives for the whole attempt, and each attempt gets its own local token instead of overwriting the captured parameter.

Since the CancellationTokenSource now takes a TimeProvider, it's also possible to test the timeout with FakeTimeProvider without having to wait 5 seconds.

Running the repro again with the fix:

TaskCanceledException after 1.0s

Two bugs in one small if statement, not bad :)

Read the whole story
alvinashcraft
2 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

535: Next-Gen Siri: Disappointingly Not the Chatbot We Wanted

1 Share

James and Frank explore Apple's next-gen Siri and iOS 27 updates with mixed results—the chatbot fell short of expectations despite hype. They dive into AI agents, smart home automation potential, and why Apple's scattered AI integration lags behind competitors like OpenAI and Microsoft. Plus, practical developer updates on Xcode, .NET, and automating your digital life.

Follow Us

⭐⭐ Review Us ⭐⭐

Machine transcription available on http://mergeconflict.fm

Support Merge Conflict





Download audio: https://aphid.fireside.fm/d/1437767933/02d84890-e58d-43eb-ab4c-26bcc8524289/103347ec-96b2-44a1-8fb3-4bc893732449.mp3
Read the whole story
alvinashcraft
2 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Ephemeral testing

1 Share
We have many ways to ensure software quality. Unit testing. Fuzz testing. Integration testing. And so forth.
 
I’d like to propose a method that was unthinkable before: ephemeral testing. (Ephemeral is a fancy word for ‘throw away’ or ‘temporary’.)
 
You write your code. You build your software component. Or the AI agent does it for you, it does not matter.
 
Then you ask an AI agent to build on it: an application, another layer, maybe several. You have it test what it built. You do not assess the original work directly. You assess how good the software built on top of it is.
 
It is a form of integration testing. The difference is that the software on top is entirely ephemeral. You throw it away when you are done.
 
A library with a clean API, stable invariants, and useful errors lets the agent produce something that works quickly. A library with hidden state, surprising defaults, or incomplete docs produces a pile of patches and failures. The failures are evidence about your code, not about the agent.
 
You can repeat it. Different agents, different tasks, same foundation.
 
In effect, instead of building the core while trying to anticipate what might be needed at the other layers, you just simulate the other layers by actually building them.
 
Of course, you could argue that with AI, you can rebuild everything whenever you need to. But that’s not practical. You need some form of stability.
 
I have been applying this trick to various projects. As I consider a new feature, I ask my AI to prototype quickly what I might later build based on what I am doing it. Ephemeral testing works for me thus far.
Read the whole story
alvinashcraft
3 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

One Repository, Two AI Assistants: Structuring Guidance for GitHub Copilot and Claude Code

1 Share
AI tools are exploding; some are better at one task than another, so teams try to work with multiple. How do we apply this concept and ensure everyone shares a common understanding without duplicating a crazy amount of instructions that may not be properly maintained? Let's explore some options.
Read the whole story
alvinashcraft
3 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Aspire 13.6 Adds Persistent Dashboard Telemetry and First-Party Java and Rust Hosting

1 Share

Microsoft has released Aspire 13.6. The dashboard now stores telemetry in SQLite and keeps up to ten completed runs per application. The release adds prerelease hosting packages for Java and Rust, portable volume paths, and new CLI options. Breaking changes include automatic TLS for local MongoDB resources and a new default Cosmos DB emulator image.

By Almir Vuk
Read the whole story
alvinashcraft
3 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories