A 2.4 million-line codebase (a codebase of 2.4 million lines of source code, which is roughly twenty to thirty times the size of the Linux kernel) that has not seen a major language upgrade in sixteen years is going to have a long migration. EVE Online, the spaceship-MMO (massively multiplayer online game) run by CCP Games in Iceland, announced on 25 August 2026 that the work has begun: their backend is moving from Stackless Python 2.7 to CPython 3, via the futurize script (a tool that mechanically rewrites Python 2 source to be compatible with both Python 2 and Python 3 as a first migration pass) followed by manual review of the places where the two languages genuinely behave differently. The announcement, surfaced via Simon Willison’s link blog, is short on timelines and long on the philosophical part: explaining why the team kept a Python 2 fork alive for as long as it did, and what it took to finally let go.
Why EVE was on Stackless in the first place
The short version of the history is that EVE launched in 2003 on Stackless Python (a variant of CPython that removes the global interpreter lock and adds microthreads called “tasklets”, enabling massive concurrency in a single Python process), which is itself a fork of CPython. At the time, Stackless was the only practical way to run tens of thousands of concurrent in-game objects in one process without a thread pool the size of a small datacenter. The team’s last major upgrade was to Stackless Python 2.7 in 2010, the same year Python 2.7 itself shipped. Sixteen years later, the upstream Python 2 line is end-of-life (Python 2 reached its official end-of-life on 1 January 2020 and stopped receiving security fixes), the Stackless 2.7 fork has accumulated its own patches that no longer merge cleanly, and the developer pool that knows the quirks of pre-3 Python is shrinking every quarter. Migration is not a choice anymore. It is a forced march.
The numbers that tell the story
What catches the eye in the announcement is the size of the codebase and the size of the manual review queue. 2.4 million lines of Python is a small city of code. 20,000 sites where Python 2 and Python 3 genuinely differ is the number CCP is calling out for manual review, which is a high enough count to be honest about the size of the work but a small enough fraction (less than 1% of the codebase) to suggest the futurize step handles most of the mechanical rewrites. The classic example is integer division: in Python 2, 1 / 2 is 0 because both operands are integers; in Python 3, it is 0.5 because / is true division. There is a // operator for floor division in both, and the fix is mechanical, but the meaning of the existing code has to be confirmed by a human at every site. Doing 20,000 of those is roughly a year’s worth of careful work for a small team, even with tooling.
What replaces Stackless
What the announcement does not say is what will replace Stackless. The team has been public about the replacement they built for their newer game, EVE Frontier, in a 2025 conference talk titled “Scheduling in Carbon: Leaving Stackless Python Behind” (the slides are linked from CCP’s developer blog, and the code is now open source at carbonengine/scheduler on GitHub). Carbon is the engine that runs EVE Frontier, and it uses an explicit scheduler library rather than Stackless’s tasklets. They have already gone through the Stackless-removal exercise once, which is the most concrete signal that the main EVE migration is feasible. They have a working pattern; they just have to apply it to a much larger codebase.
What I would tell past me about Python 2 to 3 migrations
- The mechanical pass is not the hard part. Tools like
futurize,2to3, andpyupgradehandle the syntax changes (print as function,exceptchaining, integer division) reliably. They run in CI (continuous integration, the automated build-and-test pipeline that runs on every code change) and they are deterministic. Budget two weeks for the initial pass, not two months. - The hard part is the semantic diff. Every
map(), everyfilter(), everyzip(), everydict.keys()iteration, everysorted()with a comparator, everyreduce()is a place where Python 2 and Python 3 can return different things on the same input. Static analysis catches most of these. Tests catch the rest. If you do not have tests, that is a different migration that has to happen first. - The
futurizeintermediate step is a kindness to yourself. Runningfuturize --stage1produces code that runs on both Python 2 and Python 3, which lets you deploy to production on 2.7 while running your test suite on 3. You can ship the change behind a flag, watch the metrics, and only then remove the 2.7 support code. This is what the big Python shops (Instagram, Dropbox, YouTube) did in 2017-2019, and it is the recipe that scales. - Custom forks die slowly. Once a project has been on a Stackless-style or mod_wsgified or cffi-shimmed Python fork for ten years, the fork has its own bugs, its own security patches, and its own onboarding story. The cost of staying on the fork rises every year, and the cost of leaving is mostly the upfront migration work. Most teams underestimate the first and overestimate the second.
Trade-offs
The big trade-off in any Python 2 to 3 migration is when to flip the default. Some teams run both interpreters in production for a year, with a feature flag per request, which gives the safest possible rollout but doubles the test matrix and the on-call surface. Other teams do a hard cutover on a single weekend, with a full team on standby and a rollback plan that involves restoring a database snapshot, which is cheaper in the long run but concentrates all the risk into a 48-hour window. CCP’s choice of futurize first, then manual review, then cutover, is the third option, which is the slowest to start but the easiest to defend in a postmortem if anything goes wrong. There is no universally correct answer; the answer depends on the size of the test suite, the size of the on-call team, and how much revenue the application generates per hour.
A second trade-off is whether to keep the Stackless-style tasklet model or to migrate to the explicit scheduler CCP built for Carbon. The tasklet model is what made Stackless attractive in the first place, and rewriting all the code that uses it to use a different concurrency model is its own multi-year project. Keeping tasklets would require a Python 3 fork of Stackless, which is yet another custom fork to maintain. Dropping tasklets means rewriting a lot of game logic, but the team already has a working pattern in Carbon. The decision to drop tasklets is implicit in the choice to migrate to CPython 3 at all, and the announcement’s silence on the subject is a tell.
A third trade-off is the cost of telling users. EVE has been running on the same backend for twenty-three years, and there are third-party tools, libraries, and data pipelines that depend on the Python 2 quirks in the public API (the API used by EVE’s third-party developer community for tools like ship fitting planners and market data scrapers). A clean cutover will break some of those, and CCP’s customer-relations job is to make sure the breakage is announced in advance, documented, and compensated for. The migration cost is not just the engineering work; it is the social work of being a good steward of an API that has been stable since the Bush administration.