Skip to content

Commit b9283eb

Browse files
authored
Add DOTNET_WORKER_<ver>_DIR environment variable (#861)
1 parent b6348d6 commit b9283eb

File tree

3 files changed

+236
-14
lines changed

3 files changed

+236
-14
lines changed

src/csharp/Microsoft.Spark.E2ETest/SparkFixture.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class EnvironmentVariableNames
3535
/// <summary>
3636
/// This environment variable specifies the path where the DotNet worker is installed.
3737
/// </summary>
38-
public const string WorkerDir = Services.ConfigurationService.WorkerDirEnvVarName;
38+
public const string WorkerDir = Services.ConfigurationService.DefaultWorkerDirEnvVarName;
3939
}
4040

4141
private readonly Process _process = new Process();
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using Microsoft.Spark.Services;
8+
using Microsoft.Spark.Utils;
9+
using Xunit;
10+
11+
namespace Microsoft.Spark.UnitTest
12+
{
13+
public class ConfigurationServiceTests : IDisposable
14+
{
15+
private readonly WorkerDirEnvVars _workerDirEnvVars;
16+
17+
public ConfigurationServiceTests()
18+
{
19+
var version = new Version(AssemblyInfoProvider.MicrosoftSparkAssemblyInfo().AssemblyVersion);
20+
_workerDirEnvVars = new WorkerDirEnvVars
21+
{
22+
WorkerDir = new EnvVar(ConfigurationService.DefaultWorkerDirEnvVarName),
23+
WorkerMajorMinorBuildDir = new EnvVar(
24+
string.Format(
25+
ConfigurationService.WorkerVerDirEnvVarNameFormat,
26+
$"{version.Major}_{version.Minor}_{version.Build}")),
27+
WorkerMajorMinorDir = new EnvVar(
28+
string.Format(
29+
ConfigurationService.WorkerVerDirEnvVarNameFormat,
30+
$"{version.Major}_{version.Minor}")),
31+
WorkerMajorDir = new EnvVar(
32+
string.Format(ConfigurationService.WorkerVerDirEnvVarNameFormat, version.Major))
33+
};
34+
35+
Environment.SetEnvironmentVariable(_workerDirEnvVars.WorkerDir.Name, null);
36+
Environment.SetEnvironmentVariable(_workerDirEnvVars.WorkerMajorMinorBuildDir.Name, null);
37+
Environment.SetEnvironmentVariable(_workerDirEnvVars.WorkerMajorMinorDir.Name, null);
38+
Environment.SetEnvironmentVariable(_workerDirEnvVars.WorkerMajorDir.Name, null);
39+
}
40+
41+
[Fact]
42+
public void TestWorkerExePathWithNoEnvVars()
43+
{
44+
var configService = new ConfigurationService();
45+
46+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorBuildDir.Name));
47+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorDir.Name));
48+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorDir.Name));
49+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerDir.Name));
50+
51+
// Environment variables not set, only Microsoft.Spark.Worker filename should be returned.
52+
Assert.Equal(ConfigurationService.ProcFileName, configService.GetWorkerExePath());
53+
}
54+
55+
[Fact]
56+
public void TestWorkerExePathWithWorkerDirEnvVar()
57+
{
58+
var configService = new ConfigurationService();
59+
string workerDir = "workerDir";
60+
Environment.SetEnvironmentVariable(_workerDirEnvVars.WorkerDir.Name, workerDir);
61+
62+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorBuildDir.Name));
63+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorDir.Name));
64+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorDir.Name));
65+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerDir.Name));
66+
67+
// Only WorkerDir is set, WorkerExePath will be built using it.
68+
Assert.Equal(
69+
Path.Combine(workerDir, ConfigurationService.ProcFileName),
70+
configService.GetWorkerExePath());
71+
}
72+
73+
[Fact]
74+
public void TestWorkerExePathWithEnvVarPrecedence()
75+
{
76+
{
77+
var configService = new ConfigurationService();
78+
string workerDir = "workerDir";
79+
Environment.SetEnvironmentVariable(_workerDirEnvVars.WorkerDir.Name, workerDir);
80+
81+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorBuildDir.Name));
82+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorDir.Name));
83+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorDir.Name));
84+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerDir.Name));
85+
Assert.Equal(
86+
Path.Combine(workerDir, ConfigurationService.ProcFileName),
87+
configService.GetWorkerExePath());
88+
}
89+
90+
{
91+
var configService = new ConfigurationService();
92+
string workerMajorDir = "workerMajorDir";
93+
Environment.SetEnvironmentVariable(_workerDirEnvVars.WorkerMajorDir.Name, workerMajorDir);
94+
95+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorBuildDir.Name));
96+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorDir.Name));
97+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerMajorDir.Name));
98+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerDir.Name));
99+
Assert.Equal(
100+
Path.Combine(workerMajorDir, ConfigurationService.ProcFileName),
101+
configService.GetWorkerExePath());
102+
}
103+
104+
{
105+
var configService = new ConfigurationService();
106+
string workerMajorMinorDir = "workerMajorMinorDir";
107+
Environment.SetEnvironmentVariable(
108+
_workerDirEnvVars.WorkerMajorMinorDir.Name, workerMajorMinorDir);
109+
110+
Assert.False(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorBuildDir.Name));
111+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorDir.Name));
112+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerMajorDir.Name));
113+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerDir.Name));
114+
Assert.Equal(
115+
Path.Combine(workerMajorMinorDir, ConfigurationService.ProcFileName),
116+
configService.GetWorkerExePath());
117+
}
118+
119+
{
120+
var configService = new ConfigurationService();
121+
string workerMajorMinorBuildDir = "workerMajorMinorBuildDir";
122+
Environment.SetEnvironmentVariable(
123+
_workerDirEnvVars.WorkerMajorMinorBuildDir.Name, workerMajorMinorBuildDir);
124+
125+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorBuildDir.Name));
126+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerMajorMinorDir.Name));
127+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerMajorDir.Name));
128+
Assert.True(IsEnvVarSet(_workerDirEnvVars.WorkerDir.Name));
129+
Assert.Equal(
130+
Path.Combine(workerMajorMinorBuildDir, ConfigurationService.ProcFileName),
131+
configService.GetWorkerExePath());
132+
}
133+
}
134+
135+
public void Dispose()
136+
{
137+
Environment.SetEnvironmentVariable(
138+
_workerDirEnvVars.WorkerDir.Name,
139+
_workerDirEnvVars.WorkerDir.Value);
140+
Environment.SetEnvironmentVariable(
141+
_workerDirEnvVars.WorkerMajorMinorBuildDir.Name,
142+
_workerDirEnvVars.WorkerMajorMinorBuildDir.Value);
143+
Environment.SetEnvironmentVariable(
144+
_workerDirEnvVars.WorkerMajorMinorDir.Name,
145+
_workerDirEnvVars.WorkerMajorMinorDir.Value);
146+
Environment.SetEnvironmentVariable(
147+
_workerDirEnvVars.WorkerMajorDir.Name,
148+
_workerDirEnvVars.WorkerMajorDir.Value);
149+
}
150+
151+
public bool IsEnvVarSet(string name) =>
152+
!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(name));
153+
154+
private class WorkerDirEnvVars
155+
{
156+
public EnvVar WorkerDir { get; set; }
157+
public EnvVar WorkerMajorMinorBuildDir { get; set; }
158+
public EnvVar WorkerMajorMinorDir { get; set; }
159+
public EnvVar WorkerMajorDir { get; set; }
160+
}
161+
162+
private class EnvVar
163+
{
164+
public string Name { get; }
165+
public string Value { get; }
166+
167+
public EnvVar(string name)
168+
{
169+
Name = name;
170+
Value = Environment.GetEnvironmentVariable(name);
171+
}
172+
173+
}
174+
}
175+
}

src/csharp/Microsoft.Spark/Services/ConfigurationService.cs

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,72 @@ namespace Microsoft.Spark.Services
1616
/// </summary>
1717
internal sealed class ConfigurationService : IConfigurationService
1818
{
19-
public const string WorkerDirEnvVarName = "DOTNET_WORKER_DIR";
2019
public const string WorkerReadBufferSizeEnvVarName = "spark.dotnet.worker.readBufferSize";
2120
public const string WorkerWriteBufferSizeEnvVarName =
2221
"spark.dotnet.worker.writeBufferSize";
2322

23+
internal const string DefaultWorkerDirEnvVarName = "DOTNET_WORKER_DIR";
24+
internal const string WorkerVerDirEnvVarNameFormat = "DOTNET_WORKER_{0}_DIR";
25+
2426
private const string DotnetBackendPortEnvVarName = "DOTNETBACKEND_PORT";
2527
private const int DotnetBackendDebugPort = 5567;
2628

2729
private const string DotnetNumBackendThreadsEnvVarName = "DOTNET_SPARK_NUM_BACKEND_THREADS";
2830
private const int DotnetNumBackendThreadsDefault = 10;
2931

3032
private static readonly string s_procBaseFileName = "Microsoft.Spark.Worker";
31-
private static readonly string s_procFileName =
32-
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
33-
$"{s_procBaseFileName}.exe" :
34-
s_procBaseFileName;
3533

3634
private readonly ILoggerService _logger =
3735
LoggerServiceFactory.GetLogger(typeof(ConfigurationService));
3836

3937
private string _workerPath;
38+
private string _workerDirEnvVarName;
39+
40+
/// <summary>
41+
/// The Microsoft.Spark.Worker filename.
42+
/// </summary>
43+
internal static string ProcFileName { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
44+
$"{s_procBaseFileName}.exe" :
45+
s_procBaseFileName;
46+
47+
/// <summary>
48+
/// Returns the environment variable name that defines the path to the
49+
/// Microsoft.Spark.Worker. Using the Microsoft.Spark assembly version
50+
/// we use the first environment variable that is defined in the
51+
/// following order and default to DOTNET_WORKER_DIR if not:
52+
///
53+
/// - DOTNET_WORKER_{MAJOR}_{MINOR}_{BUILD}_DIR
54+
/// - DOTNET_WORKER_{MAJOR}_{MINOR}_DIR
55+
/// - DOTNET_WORKER_{MAJOR}_DIR
56+
/// </summary>
57+
internal string WorkerDirEnvVarName
58+
{
59+
get
60+
{
61+
if (_workerDirEnvVarName != null)
62+
{
63+
return _workerDirEnvVarName;
64+
}
65+
66+
var version = new Version(AssemblyInfoProvider.MicrosoftSparkAssemblyInfo().AssemblyVersion);
67+
var versionComponents = new int[] { version.Major, version.Minor, version.Build };
68+
for (int i = versionComponents.Length; i > 0; --i)
69+
{
70+
var span = new ReadOnlySpan<int>(versionComponents, 0, i);
71+
string verEnvVarName = string.Format(
72+
WorkerVerDirEnvVarNameFormat,
73+
string.Join("_", span.ToArray()));
74+
if (!string.IsNullOrWhiteSpace(GetEnvironmentVariable(verEnvVarName)))
75+
{
76+
_workerDirEnvVarName = verEnvVarName;
77+
return _workerDirEnvVarName;
78+
}
79+
}
80+
81+
_workerDirEnvVarName = DefaultWorkerDirEnvVarName;
82+
return _workerDirEnvVarName;
83+
}
84+
}
4085

4186
/// <summary>
4287
/// How often to run GC on JVM ThreadPool threads. Defaults to 5 minutes.
@@ -45,7 +90,7 @@ public TimeSpan JvmThreadGCInterval
4590
{
4691
get
4792
{
48-
string envVar = Environment.GetEnvironmentVariable("DOTNET_JVM_THREAD_GC_INTERVAL");
93+
string envVar = GetEnvironmentVariable("DOTNET_JVM_THREAD_GC_INTERVAL");
4994
return string.IsNullOrEmpty(envVar) ? TimeSpan.FromMinutes(5) : TimeSpan.Parse(envVar);
5095
}
5196
}
@@ -59,7 +104,7 @@ public TimeSpan JvmThreadGCInterval
59104
public int GetBackendPortNumber()
60105
{
61106
if (!int.TryParse(
62-
Environment.GetEnvironmentVariable(DotnetBackendPortEnvVarName),
107+
GetEnvironmentVariable(DotnetBackendPortEnvVarName),
63108
out int portNumber))
64109
{
65110
_logger.LogInfo($"'{DotnetBackendPortEnvVarName}' environment variable is not set.");
@@ -77,7 +122,7 @@ public int GetBackendPortNumber()
77122
public int GetNumBackendThreads()
78123
{
79124
if (!int.TryParse(
80-
Environment.GetEnvironmentVariable(DotnetNumBackendThreadsEnvVarName),
125+
GetEnvironmentVariable(DotnetNumBackendThreadsEnvVarName),
81126
out int numThreads))
82127
{
83128
numThreads = DotnetNumBackendThreadsDefault;
@@ -97,19 +142,21 @@ public string GetWorkerExePath()
97142
return _workerPath;
98143
}
99144

100-
string workerDir = Environment.GetEnvironmentVariable(WorkerDirEnvVarName);
101-
102145
// If the WorkerDirEnvName environment variable is set, the worker path is constructed
103146
// based on it.
147+
string workerDir = GetEnvironmentVariable(WorkerDirEnvVarName);
104148
if (!string.IsNullOrEmpty(workerDir))
105149
{
106-
_workerPath = Path.Combine(workerDir, s_procFileName);
107-
_logger.LogDebug($"Using the environment variable to construct .NET worker path: {_workerPath}.");
150+
_workerPath = Path.Combine(workerDir, ProcFileName);
151+
_logger.LogDebug(
152+
"Using the {0} environment variable to construct .NET worker path: {1}.",
153+
WorkerDirEnvVarName,
154+
_workerPath);
108155
return _workerPath;
109156
}
110157

111158
// Otherwise, the worker executable name is returned meaning it should be PATH.
112-
_workerPath = s_procFileName;
159+
_workerPath = ProcFileName;
113160
return _workerPath;
114161
}
115162

0 commit comments

Comments
 (0)