Upgrading to .NET 8: Part 6 - The Stable Release

Highlights from upgrading to the stable release of .NET 8

20 November 2023 by Martin Costello |
20 November 2023 by Martin Costello

Last week at .NET Conf 2023, the stable release of .NET 8 was released as the latest Long Term Support (LTS) release of the .NET platform.

With the release of .NET 8.0.0 and the end of the preview releases, my past week can be summed up by the following image:

The All the Things meme, with the text: Upgrade All The Things To .NET 8

Upgrading All The Things

You may think that's an exaggeration, but it's not far from the truth. ๐Ÿ˜…

Over the past 7 days I (or my automation) have upgraded the default branch of over 60 GitHub repositories to use the stable release .NET 8, from either .NET 6/7 or .NET 8 Release Candidate 2.

Highlights of the upgrade have included:

An (Almost) Pain Free Upgrade

Through testing with the preview releases over this release cycle, the upgrade to the stable release was pretty much a non-event, everything Just Workedโ„ข๏ธ.

There was only one thing which caught me out, and that was that the Microsoft.Extensions.Http.Telemetry NuGet package was renamed between RC2 and the stable release to Microsoft.Extensions.Http.Diagnostics. This was temporarily confusing as the annoucement blog post included the old name, but the 8.0.0 NuGet package was not available. I queried this with the team and they confirmed the name change the next day.

Within 24 hours of the release, I'd updated all of the applications and libraries I'm responsible for maintaining to either target .NET 8, or build with the .NET 8 SDK. ๐Ÿš€

There was however a tangential issue I stumbled into while working through all the updates for both my open source repositories and projects I help maintain internally at work.

Amazon Linux 2023

On the 9th of November, AWS announced support for Amazon Linux 2023 as a custom runtime for AWS Lambda. I've been waiting for this to be released for a while as it has been in preview for quite some time, and I've been using the Amazon Linux 2 custom runtime since it was released. Amazon Linux 2023 also looks like it'll be the basis for any forthcoming AWS Lambda managed runtime support for .NET 8 (soon I hope ๐Ÿคž) - it's already used as the basis for the new Node.js 20.x and Java 21 managed runtimes.

I was just leaving for a short break in Norway when the release was announced, so I didn't see the annoucement until I next sat in front of a computer again on the 14th of November. As I was already (or about to) update a bunch of applications for .NET 8 later in the day, I figured I'd get ahead of the curve and update the AWS Lambda functions I maintain to use the new runtime in advance of those changes.

Things were all pretty painless as with the subsequent .NET 8 updates...or so I thought.

A few days later I noticed that some non-critical automation we use internally to deploy GitHub releases had stopped auto-approving releases deemed as "safe" for automated deployment. I dug into the error logs for the application in Kibana and found the following warning logged:

The value Europe/London could not be parsed as a TimeZoneInfo value.

A little refactoring later and adjusting the logging lead to the following exception that was being swallowed on the assumption it would only fail for mis-configured time zone IDs:

System.TimeZoneNotFoundException: The time zone ID 'Europe/London' was not found on the local computer.
 ---> System.IO.DirectoryNotFoundException: Could not find a part of the path '/usr/share/zoneinfo/Europe/London'.
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, UnixFileMode openPermissions, Int64& fileLength, UnixFileMode& filePermissions, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.TimeZoneInfo.ReadAllBytesFromSeekableNonZeroSizeFile(String path, Int32 maxFileSize)
   at System.TimeZoneInfo.TryGetTimeZoneFromLocalMachineCore(String id, TimeZoneInfo& value, Exception& e)

The failures were coming from the calls to TimeZoneInfo.FindSystemTimeZoneById(string) used to convert UTC DateTimeOffset values to their local time zone equivalents. The failure was causing release window definitions used by the automation to be failed to be parsed, so the code was failing safe and not auto-approving the deployments as it didn't know whether it was a safe time of day to do so.

A little more probing around on the Amazon Linux 2023 instead lead me to the conclusion that the tzdb package isn't installed on Amazon Linux 2023. That's an unfortunate change compared to Amazon Linux 2, where it is installed by default. I couldn't find any documentation for Amazon Linux 2023 either way on whether it should or shouldn't have been installed, so I raised an issue with the AWS .NET team.

It turns out that this is a known issue the AWS Lambda team are aware of, but there's currently no guidance on what to do instead or whether the distribution will be changed to include the tzdb package by default.

I can see the rationale for removing it to make the distribution smaller, but time zone handling is quite a common requirement for applications, so it's a shame it's not included by default as it breaks the ability to use the out-of-the-box .NET APIs for time zone handling (especially with the new TimeProvider API).

In the meantime, I've worked around the issue by (re)adding a dependency on the NodaTime NuGet package to handle time zone conversions.

NodaTime contains an embedded version of the tzdb database, which can be easily used to convert between time zones. I would prefer to not use NodaTime to keep the deployed application size down and not have an extra NuGet dependency to keep up to date, but it's a small change to make.

The code changes required for this scenario are pretty simple, as the following example shows.

Before the code was essentially this:

var timeZoneId = "Europe/London";
var utcNow = TimeProvider.System.GetUtcNow();
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
var localNow = TimeZoneInfo.ConvertTimeFromUtc(utcNow, timeZone);

Using NodaTime, the code becomes:

var timeZoneId = "Europe/London";
var utcNow = TimeProvider.System.GetUtcNow();
var timeZone = DateTimeZoneProviders.Tzdb[timeZoneId];
var localNow = Instant.FromDateTimeUtc(utcNow).InZone(timeZone).ToDateTimeUnspecified();

I hope that the AWS teams will be able to add tzdb to Amazon Linux 2023 so I can remove NodaTime again, but if it's not added to the custom runtime I certainly hope it's added to any forthcoming managed runtime for .NET 8. Otherwise, I can see a lot of developers needing to update existing code to change how they deal with time zones when running in AWS Lambda compared to using the .NET 6 managed runtime for what should otherwise be a relatively straightforward upgrade.

Summary

That's a wrap for .NET 8 for 2023 - now it becomes "just the current version" for background patching and security updates.

As well as replacing both .NET 6 and 7 as the latest and greatest release with its status of LTS, .NET 8 brought us tonnes of improvements;

We truly are spoiled by the .NET team as they continue to invest and iterate on making it a highly competitive and productive development platform.

See you in 2024 for the .NET 9 upgrade! ๐Ÿ˜Ž

Upgrading to .NET 8 Series Links

You can find links to the other posts in this series below.