Rx.NET Packaging Plan 2025
Rx.NET
Ian Griffiths provides an update on the state of Rx.NET since our last talk in June. Ian addresses the current choices around packaging Rx, focuses on the "package bloat" problem and discusses the strides we've made in addressing this issue.
Ian explores the new "Rx Gauntlet" test suite designed to validate proposed solutions and highlight the use of automated tests and Power BI reports to identify and solve packaging issues.
He also compares two primary design options for future Rx versions, weighing their advantages and potential risks.
Finally, he seeks community feedback to guide our next steps in releasing a stable Rx.NET v7 version.
You can find the detailed write up of this topic via the GitHub Discussion: Packaging Plans July 2025 #2211
Contents:
- 00:00 - Introduction and Update
- 00:12 - Addressing the Bloat Problem
- 01:34 - Evidence-Based Approach and Prototypes
- 02:09 - Introducing RX Gauntlet
- 02:56 - Testing and Results
- 06:54 - Power BI Report Insights
- 10:44 - Design Options and Future Plans
- 14:55 - Community Feedback and Next Steps
This talk was given during endjin's internal weekly "show & tell" meeting, but has been edited for public consumption.
Transcript
Back at the start of June, we put out a video about the state of Rx this year. This is kind of an update on what we've done since then.
One of the main areas of focus of that talk was the situation around the bloat problem, which is the thing we call the situation where you've written an application in .NET, it targets Windows, and you are not actually using WPF or Windows Forms, but you get a copy of them anyway because Rx sort of forces that on you if you're doing self-contained deployment.
That's a problem that has caused some projects to walk away from Rx, and we have been trying to work out how best to fix that without breaking everything for everyone else. I talked a bit about our plans last time, and one of the things I mentioned there was a plan to introduce a new way of doing tests, partly so we can avoid this sort of problem in the future, but also so that we can actually validate whether particular design approaches are going to work or not. Rather than just arguing about this, we'll do this, that we'll do that. We've had a slight sort of more practical, more evidence-driven approach because we did actually announce right towards the end of last year, yes, we're moving forward with this plan. And then, hopefully some people in the Rx community pointed out some flaws in that plan.
Fortunately we didn't go ahead and do that. Thank you Rx community for saving our necks there. And so we have moved in a more evidence-based direction. Essentially we've done a couple of things. We have actually gone ahead and built various prototypes to implement various different attempts to solve this problem. And we've written some tooling to run automated tests against those solutions because one of the problems, one of the difficulties with these problems is that there are so many little variations you can test and a lot of the problems only occur in certain, quite specific combinations of scenarios. And so unless you automate the testing, it's incredibly easy to miss things.
We talked last time, I talked last time about this proposed test suite that I called Rx Gauntlet, and that now exists. So if I can actually get the focus in the right window. There we are. So in the Rx repo today, it's currently on a separate branch, the feature/rx-gauntlet branch, but it's in there. You can go and take a look at it. There is in the Rx.NET test folder, there is a gauntlet subfolder, which contains the source code for this suite of tests. And the big difference between this and normal unit tests is that this can reach places unit tests can't reach. It can see problems that emerge not inside the code itself, but in the way that the packaging interacts with other things.
And so the thing that we can do in these tests is we can actually generate new projects on the fly and change their configuration programmatically so we can do a hundred variations. In fact, one of the tests here generates, I think 720 different variations on a theme of the basic theme being, I've written an application that uses two libraries, both of which are using Rx. And there's a huge number of variations on that, some of which run into certain problems on certain designs for how to solve the bloat problem. And so this just runs all of them. So the idea is you come up with the possible solution and you run it through the gauntlet and it will tell you how well it stands up.
We're going for this evidence-based approach where we actually build out the proposed solution and then we run it through the gauntlet and we see what happens. So you can find the actual examples. We've got several branches with various different variations on the packaging in progress. I'm not going to go through them now, but they are described in the announcement here. There's a discussion up on the repo saying what we've done, tells you about the test suite, tells you about the various versions we have prototyped and made available on the public Rx preview package feed. I'll just go to that. This is the preview package feed up on Azure DevOps.
By the way, if you go here, it's possible that you will get an access denied error. That happens if you are logged in to Azure DevOps. What happens is Azure DevOps goes, oh, you're logged in. I will try and show you the version of this page that gives you access to all the things that maintainers of the project can see. And then it goes, but you are not a maintainer of this project and goes, therefore you can't have this. However, if you're not logged in, it shows it to you just fine. Which is kind of odd, but there we are. So if you can't see this, try opening a private browser window and then you should be able to see it.
And so if we look at some of the packages in here, you can see this is the System.Reactive package. There are a bunch of 7.0-preview-something packages, and these ones here are all trying out variations on how we might solve the packaging problems and exactly what these mean. This is all described in the discussion documents, so I'm not going to go into that now.
And in some cases there's also WPF specific packages, Windows Forms specific ones, and depending on which design option we go for, there might also be a new System.Reactive.NET package. This becomes the new main face of Rx. And System.Reactive gets relegated to being a legacy facade. There's two design options. One where that does happen and one where that doesn't, and I'll come back and talk about that in just a minute.
So this gauntlet exists and what it produces is enormous quantities of JSON saying what it found for each of the test cases that you ran. So we basically spit out a thing saying, okay, this one we were trying to build for a target of .NET 8. And in this particular one we turned on the EmitDisabledTransitiveProjectReferences flag. Basically all the settings that go into this. And then we say, did we actually see a copy of WPF or Windows Forms deployed into the output of the app. So we're testing for bloat in this particular case. Actually, there's several different things we test for here.
Gauntlet tests for bloat with direct usage and through transitive references. It also checks for a potential bug that can occur with extension methods if you get certain things wrong in the design. Test for all the things that we know might go wrong with certain kinds of designs. And so you end up with lots and lots and lots and lots of JSON to pour through, which is not particularly easy to find things in. So actually finding the things that tell you something is wrong is a bit like looking for a needle in a haystack. So we have also produced this thing here. This is a Power BI report that sits on top of the data and basically pulls out the things that have the problems for you.
So this page here tries to find all of the versions that suffer from package bloat. Let me just try and narrow this down. So you can see here that Rx versions 5 and 6.0.1s, these are published versions of Rx that are up on there today. They have the bloat problem, so we didn't have it before Rx 5 because it only started to occur when self-contained deployment became a possibility and certain versions of WPF and Windows Forms shipped.
And you can see from this that all of the preview versions that we're trying don't suffer from bloat if you just use Rx directly from your application. That's what this page is showing us. This has gone through all the results and it's just showing us the ones that fail. This shows which things have the plugin bug, which is actually an older bug that was fixed, that was reported back in the Rx 2 days, was fixed in Rx 3.1, and then that fix got reverted. And Rx 4 didn't actually cause a problem at the time, but all subsequent versions have actually suffered from this bug. Our current plan is to not fix this because no one has ever actually complained about this and we think it probably doesn't matter as much now as it used to because the scenarios in which it does occur are much narrower than it used to be. But this does look for that. So if people think that is a problem, we can still use this to find it. At the moment, there isn't really a good way to solve this.
This looks for the extension method bug. So this is the thing that our former proposed workaround ran into. We thought we had a workaround for this, for preventing bloat with the existing published version of Rx, but it turns out you get build errors with certain extension methods under certain situations, and this basically looks for that. And again, this is showing that that problem exists. If you try to disable transitive framework references in Rx version 5 or version 6, it doesn't work. But all the candidate builds we have for fixing the problems, actually, it's okay. You don't get these errors because we have worked out how to work around them.
Now, this Power BI report wasn't the whole story. We also found that with the transitive referenced situation, it was all rather complicated and so we actually found it was helpful to write a notebook to do some more control processing. More to do with the fact that I'm better at notebooks than I am at Power BI. You probably could do it with Power BI, but Notebook was the quickest way to get there, and this kind of gives you ultimately a top level view of how the various options stack up against some of the more subtle problems that can occur when your application may or may not be using Rx directly, but is using it indirectly through other components. And so you can't necessarily control exactly how those things are using it. And I'm not going to go into the details here because we've published this report. You can go and read it if you are interested. But basically you get this kind of color chart showing you that some options work better than others.
That one down the bottom is one of our two design candidates that we're now still thinking of doing. You can see it's green on almost everything with one rather significant red that I'm going to talk about in a moment. And the other one we are contemplating is actually the second row in here, although that's got more yellows in it and does actually have one red there. It turns out it's more of an artifact of how this notebook works. So it's got more yellows than greens, but it doesn't suffer from this big major problem down here. So let's talk about that.
So if I go back to the webpage that has the announcement of what we're doing, it says we've essentially, we've looked, we've got four fully worked out prototypes, but we've essentially boiled it down to two that we think we're going to use. And essentially one of those retains System.Reactive as the main Rx assembly. That's today's main Rx package. It's the main Rx package today, and we want to keep it as the main Rx package in the future if possible, because that's the least disruptive thing to do, and one of these solutions does do that. The other one doesn't. So why are we contemplating one that doesn't? Well, there's a serious issue with what we've had to do to make it work. If we want to retain System.Reactive as the main package, basically we have to play tricks with how the package is created. What we end up doing is creating reference assemblies that deliberately leave out certain problematic bits of the public API. But that public API is present in the runtime assemblies, and so that means you get binary compatibility because everything is there in the runtime assemblies. But some of the problems that we used to have with the workarounds for this go away because the compiler can't see them because it will use the reference assemblies.
This seems to work, but we're kind of unhappy with it because it's a clever trick and the history of applying clever tricks to try and work around packaging problems in Rx does not look good. Basically every time we do it, it turns out we're not as clever as we think. Or more often what happens is that things change and stuff that was just fine when it was introduced goes on to cause problems as a result of other things going on in the ecosystem. And so we actually have a preference to try and keep things simple. Ideally we would be doing nothing weird, nothing unusual. We build Rx in a completely conventional way. And this would minimize the chances of things going odd in the future. But the only way we seem to be able to do that is by introducing a new main package for Rx and turning the existing one into a legacy facade. And that's the only way we can really get a clean, ordinary looking build for the main Rx package.
And so that option, introducing a new main Rx package, has the attraction of being less likely to be a liability in the long run. So that's why we are considering that as the other design option is because it's less likely to break for reasons that aren't even visible yet but that will come to haunt us in the future because that has happened several times before with Rx and we don't want to be the latest in a line of those.
So that's where we're at. We've got these two options and actually there's sort of a fudge option, which is that we do one and then the other. One possibility is that for Rx 7 we do the approach where we don't introduce a new main package. We just keep System.Reactive as the primary package for Rx as it is today. But we use this hack to deal with some of these packaging problems and if that works out fine, then great. And if it turns out to cause problems, then maybe in Rx 8, we could do the thing where we introduce a new main assembly and we relegate System.Reactive to now being just a legacy facade that's there purely for backwards compatibility purposes. We could conceivably do that in two steps. So the debate at this point now really is do we actually just go straight for the solution where we just don't mess around with the main package, or do we see if we can get away with this clever trick on the System.Reactive package so that we minimize the disruption, understanding that we might then discover that actually doesn't work after all, and we have to do the package split in the long run anyway.
So those are the choices before us. Or maybe someone has other ideas. So we've got this place to go and vote on this discussion. If you think there's a thing we've missed, then you can vote for the none of the above option here. This is not a democracy, but this will tell us how people are feeling and we'll take it into account. So we haven't decided yet, but we'd like to decide soon because we would really like to get at least a beta release of Rx 7 out there, and then a proper release as soon as possible so we could finally fix this and get back to doing other things to make Rx better rather than just trying to fix build issues.
So there we are.