Skip to content

Commit

Permalink
Use system mutex in RedistributableLocator during installation (ton…
Browse files Browse the repository at this point in the history
…ybaloney#330)

* Use system mutex in "RedistributableLocator" during installation

* Release mutex when installation goes well

---------

Co-authored-by: Anthony Shaw <anthony.p.shaw@gmail.com>
  • Loading branch information
atifaziz and tonybaloney authored Jan 13, 2025
1 parent 9aa935f commit 527f03a
Showing 1 changed file with 29 additions and 24 deletions.
53 changes: 29 additions & 24 deletions src/CSnakes.Runtime/Locators/RedistributableLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace CSnakes.Runtime.Locators;
internal class RedistributableLocator(ILogger<RedistributableLocator> logger, int installerTimeout = 360) : PythonLocator
{
private const string standaloneRelease = "20250106";
private const string MutexName = @"Global\CSnakesPythonInstall-1"; // run-time name includes Python version
private static readonly Version defaultVersion = new(3, 12, 8, 0);
protected override Version Version { get; } = defaultVersion;

Expand All @@ -34,36 +35,41 @@ protected override string GetPythonExecutablePath(string folder, bool freeThread

public override PythonLocationMetadata LocatePython()
{
string dottedVersion = $"{Version.Major}.{Version.Minor}.{Version.Build}";
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create);
var downloadPath = Path.Join(appDataPath, "CSnakes", $"python{Version.Major}.{Version.Minor}.{Version.Build}");
var downloadPath = Path.Join(appDataPath, "CSnakes", $"python{dottedVersion}");
var installPath = Path.Join(downloadPath, "python", "install");
var lockfile = Path.Join(downloadPath, "install.lock");

// Check if the install path already exists to save waiting
if (Directory.Exists(installPath) && !File.Exists(lockfile))
using var mutex = new Mutex(initiallyOwned: false, $"{MutexName}-{dottedVersion}");

try
{
return LocatePythonInternal(installPath);
}
if (!mutex.WaitOne(TimeSpan.FromSeconds(installerTimeout)))
throw new TimeoutException("Python installation timed out.");

if (File.Exists(lockfile)) // Someone else is installing, wait to finish
if (Directory.Exists(installPath))
return LocatePythonInternal(installPath);
}
catch (AbandonedMutexException)
{
// Wait until it's finished
var loopCount = 0;
while (File.Exists(lockfile))
// If the mutex was abandoned, it most probably means that the other process crashed
// and didn't even get the chance to run any clean-up. Since the state of the
// download and installation is now unreliable, start by clearing up directories and
// then proceed with the installation here.

try
{
Thread.Sleep(1000);
loopCount++;
if (loopCount > installerTimeout)
{
throw new TimeoutException("Python installation timed out.");
}
Directory.Delete(downloadPath, recursive: true);
}
catch (DirectoryNotFoundException)
{
// If the directory didn't exist, ignore it and proceed.
}
return LocatePythonInternal(installPath);
}

// Create the folder and lock file, the install path is only created at the end.
// Create the folder; the install path is only created at the end.
Directory.CreateDirectory(downloadPath);
File.WriteAllText(lockfile, "");

try
{
// Determine binary name, see https://gregoryszorc.com/docs/python-build-standalone/main/running.html#obtaining-distributions
Expand Down Expand Up @@ -102,6 +108,7 @@ public override PythonLocationMetadata LocatePython()
{
throw new PlatformNotSupportedException($"Unsupported platform: '{RuntimeInformation.OSDescription}'.");
}

string downloadUrl = $"https://github.com/astral-sh/python-build-standalone/releases/download/{standaloneRelease}/cpython-{Version.Major}.{Version.Minor}.{Version.Build}+{standaloneRelease}-{platform}.tar.zst";

// Download and extract the Zstd tarball
Expand All @@ -126,11 +133,9 @@ public override PythonLocationMetadata LocatePython()

throw;
}
finally
{
// Delete the lock file
File.Delete(lockfile);
}

mutex.ReleaseMutex(); // Everything supposedly went well so release mutex

return LocatePythonInternal(installPath);
}

Expand Down

0 comments on commit 527f03a

Please sign in to comment.