PAGI is moving to Perl 5.24
The next release of the PAGI specification raises the minimum Perl to 5.24,
and the next PAGI-Server and PAGI-Tools releases go with it. There will be a
TRIAL release first, so you can kick the tires before anything changes under
you.
If you're below 5.24 this is going to bite. The new releases won't install.
If you're on 5.18 it's worse than that: Future itself now needs 5.20, so even
the current releases fail a fresh install, and any CPAN distribution still
declaring 5.18 is about to start failing on CPAN Testers. An installation you
already have keeps running until something upgrades Future underneath it.
That's borrowed time.
I said PAGI would run on 5.18 and I meant it, so I owe you an explanation:
why the floor is moving at all, and why it's landing on 5.24 rather than the
minimum.
Where the floor comes from
The PAGI specification has exactly one dependency: Future. Receive returns a
Future, send returns a Future, the application itself returns a Future. That
is the whole interface. So Future's floor is the spec's floor, and the
reference server and the toolkit inherit it from there.
Future 0.54 came out this week and requires Perl 5.20. Future also sits under
almost everything: IO::Async, Future::AsyncAwait, Future::IO and a long list
of others all pull it in. As of this week a fresh install of nearly anything
async on 5.18 fails. PAGI included.
So the floor has to go to at least 5.20. The question is how far.
Future is required; Future::AsyncAwait is how you'll actually write it
Strictly, Future is all you need to write a PAGI application. Here is "Hello,
world" with nothing but Future, chaining each step with ->then:
use Future;
my $app = sub {
my ($scope, $receive, $send) = @_;
return Future->fail("Unsupported scope type: $scope->{type}")
if $scope->{type} ne 'http';
return $send->({
type => 'http.response.start',
status => 200,
headers => [ [ 'content-type', 'text/plain' ] ],
})->then(sub {
$send->({ type => 'http.response.body', body => "Hello, world!\n", more => 0 });
});
};
And the same application with Future::AsyncAwait:
use Future::AsyncAwait;
my $app = async sub {
my ($scope, $receive, $send) = @_;
die "Unsupported scope type: $scope->{type}" if $scope->{type} ne 'http';
await $send->({
type => 'http.response.start',
status => 200,
headers => [ [ 'content-type', 'text/plain' ] ],
});
await $send->({ type => 'http.response.body', body => "Hello, world!\n", more => 0 });
};
With two sends the callback version is just noisy. Real applications loop.
They read a request body chunk by chunk, wait on a WebSocket, race a client
against a timer. With callbacks every loop turns into recursion and every
error path is one more callback to get right. That's why I recommend
Future::AsyncAwait, and why the docs, the examples and PAGI-Tools all use it.
The spec doesn't require it. But it's how nearly all PAGI code is going to get
written, so how it behaves on a given Perl matters as much as Future does.
Why 5.24 and not 5.20
5.20 just follows Future. It doesn't fix anything. 5.24 does.
Cancellation. On 5.24 and up, when you cancel an async sub,
Future::AsyncAwait cancels whatever that sub was awaiting. Below 5.24 it
can't. The sub stops, but it stays suspended, still holding whatever it was
waiting on. That sounds like a corner case until you write the most ordinary
async code there is, a receive raced against something else:
my $event = await Future->wait_any($receive->(), $ticker->next);
When the ticker wins, wait_any cancels the receive. On 5.24 the cancel goes
all the way down and that's the end of it. On older Perls the receive is left
suspended. The next message that arrives can wake it up, and now it has taken
a message for nobody. And when the application finally drops it you get
"Suspended async sub ... lost its returning future" in the logs.
The bugs I just fixed. This isn't theoretical. This month I fixed that
exact class of bug in PAGI-Server: a cancelled receive could drop a request
body, or wedge every receive after it on the same connection. Cancelling a
receive is now safe on every transport, and the spec says a server MUST allow
it. But below 5.24 the fix needs extra machinery that exists only to paper
over the older runtime, and so does every library built on PAGI that ever
races or cancels a receive. On 5.24 the problem isn't there. I'd rather that
than patch it one module at a time.
Future::XS. Future::XS is the fast implementation of Future, and
PAGI-Server will use it if you ask (--future-xs). 0.16, also out this week,
fixes the "lost a sequence Future" warnings some of you have been seeing. It
needs 5.24. With the floor there, everyone on a supported Perl gets it.
The ten-year line. Future's maintainer told me Future will keep
supporting Perls that are at least ten years old. 5.24 shipped in May 2016,
so it sits right on that line. 5.20 is twelve years old. I'd rather PAGI's
floor line up with the promise the ecosystem is actually making than land
somewhere older for no particular reason.
There are smaller wins too (postfix dereference is stable, and CI can test
the floor directly) but they wouldn't justify the move on their own.
Signatures aren't on the list. They're only stable from 5.36, and PAGI's own
libraries will stick to syntax that runs on the floor.
The promise, going forward
So here's the policy. The ten-year line is what PAGI works to:
- PAGI won't move its floor automatically when a Perl turns ten.
- Once your Perl is more than ten years old you're in grace time. It still works, and I won't break it casually, but it's the next thing that can go.
- I can't promise advance notice of every floor change, because PAGI's floor follows its dependencies and I don't control them. What I can do is stay close to those projects and pass on what I hear as early as I can, on the pagi-announce list, so you find out from me and not from a failed install.
And to be clear about where that leaves us: 5.24 turned ten in May 2026, so
it starts life in grace time. Future's maintainer has said the next step that
would be interesting for Future is 5.26. If Future goes there, PAGI follows,
because it has to. This week was the demonstration. I'll tell you the moment
I know.
These releases also break compatibility, on purpose
The same Server and Tools releases implement the current spec (Www 0.6 and
Lifespan 0.3, published with PAGI 0.003000), and they are not drop-in
upgrades. The full list of changes is in the upgrade notes. What I want to
give you here is the reasoning.
It's cheaper now than it will ever be. PAGI is pre-1.0, and the people
building on it are few enough that I can work with each of them directly,
which I've been doing, to get their frameworks ported ahead of the release.
Every year of carrying compatibility layers makes the eventual cut more
expensive and the code harder to trust. So: one clean break this year,
instead of shims piling up.
A spec is only worth having if a server and an application can't read it two
ways. Running real servers and real frameworks against it showed where it
left room for that. The fixes all go in the direction of fewer, plainer
rules. Refusing a WebSocket or SSE connection is just an HTTP response now.
Shutdown closes connections in a way a client can recognise as a restart, and
gives your cleanup time to finish. Cancelling a receive is something an
application is allowed to do.
PAGI-Tools had grown more than one way to do most things: more than one
router, a large context object, a mutable response builder. Each one made
applications harder to read and harder to compose. The new release keeps one
of each, built so a CPAN module that uses them never ties its users to a
particular event loop.
And the test client now refuses exactly what the real server refuses. If
your tests pass, that means something.
The details, with a before and after for each change, are in the upgrade
notes: PAGI::Upgrading for the specification, and UPGRADING.md in PAGI-Server
and PAGI-Tools.
What to do
- Everyone: join pagi-announce. That's where changes to the specification and the ecosystem around it get announced.
- On 5.24 or newer: try the TRIAL when it lands (
cpanm --dev PAGI::Server PAGI::Tools) and tell me what breaks. - On 5.18 to 5.22: start planning the move to a newer Perl. Until then, don't let anything upgrade Future in an installation that works. On 5.18 a fresh install needs Future 0.53 or older, installed first.
- Building something on PAGI: read the upgrade notes and get in touch. I'm happy to help with the port, same as I have for the frameworks already on board.
Thanks to everyone who weighed in on the mailing list.







