-
Notifications
You must be signed in to change notification settings - Fork 664
DYN-9348 Integrator PackagePath following Integration version #16528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DYN-9348 Integrator PackagePath following Integration version #16528
Conversation
After providing the context to GitHub Copilot for updating the PathManager lazy loading singleton and updating the PathManagerParams in the constructor, the next alternatives were provided by Copilot: Set Before First Access (Recommended) - Adding a static Initialize method Pass via IPathResolver Modify the Lazy Initialization Logic Set Static Properties Before First Use Recreate the Singleton (Not Recommended) In this PR is implemented the first one with some minor changes based in GitHub Copilot suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the ticket for this pull request: https://jira.autodesk.com/browse/DYN-9348
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes installing Dynamo packages using the DynamoRevit version-specific path by implementing a singleton initialization pattern for PathManager. The fix allows the PathManager to be initialized with host version information before first access, ensuring the correct package path is used based on the host's major and minor version numbers.
Key changes:
- Added static initialization method for PathManager singleton with version parameters
- Modified DynamoModel constructor to initialize PathManager with host version information
- Replaced immediate lazy initialization with deferred initialization pattern
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
File | Description |
---|---|
src/DynamoCore/Models/DynamoModel.cs | Added initialization logic to set PathManager with host version from configuration |
src/DynamoCore/Configuration/PathManager.cs | Refactored singleton pattern to support initialization with version parameters before first access |
if (!isInitialized) | ||
{ | ||
lazy = new Lazy<PathManager>(() => new PathManager(parameters)); | ||
isInitialized = true; | ||
} |
Copilot
AI
Sep 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Initialize method is not thread-safe. Multiple threads could simultaneously check isInitialized
as false and both execute the initialization block. Consider using a lock or making this method thread-safe with double-checked locking pattern.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think copilot is bringing this up because one of the main use cases of Lazy<T>
is to make initialization thread safe when multiple threads might first initialize an object.
Because the pathmanager was made static - a bad design decision IMO - I imagine that making it lazy tries to be defensive, but the path manager itself is not thread safe AFAIK?
Anyway - my gut feeling is:
- This PR adds minimal complexity to an already hard to reason about piece of Dynamo, so I am okay with that.
- It would be nice to rip this singleton out and rethink it.
- The one thing I will add about alternatives is - should we in DynamoCore really be in control of these paths? It might make sense allow clients to override this by using a new member in the
IPathResolver
interface.
@QilongTang - am I remembering correctly that somehow Civil3d worked around this issue and put packages wherever they wanted? How did they do that?
if (lazy == null) | ||
{ | ||
// Fallback to default if not initialized | ||
lazy = new Lazy<PathManager>(() => new PathManager(new PathManagerParams())); | ||
isInitialized = true; | ||
} |
Copilot
AI
Sep 26, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Instance property getter is not thread-safe. Multiple threads could simultaneously check lazy == null
and both execute the fallback initialization. This could lead to race conditions and inconsistent state.
Copilot uses AI. Check for mistakes.
@QilongTang this one was the best solution that Copilot provided for setting the PathManagerParams in the PathManager constructor, I tested the fix in a different branch (before .NET 10 changes) and Revit Preview Release (2025) - using latest changes in DynamoRevit repo. |
lazy = | ||
new Lazy<PathManager> | ||
(() => new PathManager(new PathManagerParams())); | ||
private static Lazy<PathManager> lazy; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this implementation Lazy
the docs say:
Use lazy initialization to defer the creation of a large or resource-intensive object, or the execution of a resource-intensive task, particularly when such creation or execution might not occur during the lifetime of the program.
I think it's unlikely any of our clients will not need a path manager.
If it was to make initialization thread safe, well then, this PR breaks that invariant, so whats the point?
this PR:
|
@RobertGlobant20 can you explain why you need to introduce |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @RobertGlobant20, after much debugging and considering different options, here are my thoughts and recommendations. Please let me know what you think about them.
The indeterministic nature of PathManager
initialization causes it to be initialized with empty PathManagerParams()
if PathManager.Instance
is accessed before host applications (Revit, Civil3D) have a chance to supply DynamoModel
with their desired configurations.
Note that the default empty PathManagerParams()
does initialize PathManager
with sensible defaults, but these don't necessarily match what the host application has in mind. For example, running Dynamo in Revit gives us the defaults shown below (note that this is without your sample path fix in PR #16578, so we still see ProgramData
below):
Path | Value |
---|---|
BackupDirectory | %AppData%\Dynamo\Dynamo Core\backup |
DefaultPackagesDirectory | %AppData%\Dynamo\Dynamo Core\4.0\packages |
DefaultUserDefinitions | %AppData%\Dynamo\Dynamo Core\4.0\definitions |
LogDirectory | %AppData%\Dynamo\Dynamo Core\4.0\Logs |
PreferenceFilePath | %AppData%\Dynamo\Dynamo Core\4.0\DynamoSettings.xml |
PythonTemplateFilePath | %AppData%\Dynamo\Dynamo Core\4.0\PythonTemplate.py |
UserDataDirectory | %AppData%\Dynamo\Dynamo Core\4.0 |
CommonDataDirectory | %ProgramData%\Dynamo\Dynamo Core\4.0 |
DefaultTemplatesDirectory | %ProgramData%\Dynamo\Dynamo Core\templates\en-US |
SamplesDirectory | %ProgramData%\Dynamo\Dynamo Core\samples\en-US |
DynamoCoreDirectory | %UserProfile%\Documents\dev\Dynamo\bin\AnyCPU\Debug |
Tweaks Needed
I agree with @mjkkirschner 100% that the singleton static PathManager
is a bad idea - it needs a design overhaul, and I'd opt to rip this singleton out in a heartbeat. However, we are too close to the release date for that. So here's an alternative I'm proposing to keep the risk to a minimum.
This PR is quite close to the "ideal" way, and is frankly the better approach compared to PR #16460. It just needs a few tweaks:
-
Remove the
bool PathManager.isInitialized
variable - checkingif (lazy == null)
is sufficient. -
As Copilot suggested, use a
lock
for thread safety:private static readonly object lockObject = new object(); public static void Initialize(PathManagerParams parameters) { lock (lockObject) { if (lazy != null) { // Or do we want to reset the existing instance? See below for discussions. throw new InvalidOperationException("PathManager has already been initialized."); } lazy = new Lazy<PathManager>(() => new PathManager(parameters)); } } public static PathManager Instance { get { if (lazy == null) { lock (lockObject) { if (lazy == null) { // Fallback to default if not initialized lazy = new Lazy<PathManager>(() => new PathManager(new PathManagerParams())); } } } return lazy.Value; } }
-
In
DynamoModel.CreatePathManager()
, we will call your newPathManager.Initialize()
method:internal PathManager CreatePathManager(IStartConfiguration config) { var pathManagerParams = new PathManagerParams(); var version = config.HostAnalyticsInfo.HostVersion; if (version != null) { // Use host versions if provided pathManagerParams.MajorFileVersion = version.Major; pathManagerParams.MinorFileVersion = version.Minor; } Dynamo.Core.PathManager.Initialize(pathManagerParams); // Existing code... if (!config.StartInTestMode) { } }
-
After
AssignHostPathAndIPathResolver
is called with the host path, the paths will be as shown below, which are quite sensible (again, this is without your sample path fix in PR #16578, so we still seeProgramData
below):Path Value BackupDirectory %AppData%\Dynamo\Dynamo Revit\backup DefaultBackupDirectory %AppData%\Dynamo\Dynamo Revit\backup DefaultPackagesDirectory %AppData%\Dynamo\Dynamo Revit\4.0\packages DefaultUserDefinitions %AppData%\Dynamo\Dynamo Revit\4.0\definitions LogDirectory %AppData%\Dynamo\Dynamo Revit\4.0\Logs PreferenceFilePath %AppData%\Dynamo\Dynamo Revit\4.0\DynamoSettings.xml PythonTemplateFilePath %AppData%\Dynamo\Dynamo Revit\4.0\PythonTemplate.py UserDataDirectory %AppData%\Dynamo\Dynamo Revit\4.0 CommonDataDirectory %ProgramData%\Autodesk\RVT 2027\Dynamo\4.0 DefaultTemplatesDirectory %ProgramData%\Autodesk\RVT 2027\Dynamo\templates\en-US SamplesDirectory %ProgramData%\Autodesk\RVT 2027\Dynamo\samples\en-US DynamoCoreDirectory %UserProfile%\Documents\dev\Dynamo\bin\AnyCPU\Debug HostApplicationDirectory %UserProfile%\Documents\dev\DynamoRevit\bin\NET100\Debug\Revit -
Finally,
EnsureDirectoryExistence
will be called to actually create these directories, so there won't be any race conditions - the paths will have the correct values when directories are created.
Final Thoughts
-
We still need to remove the
IPathResolver.CommonDataRootFolder
property as we move things toAppData
. Both Revit and Civil3D are providing this value right now. If we'd like to keep it for compatibility reasons, we may want to mark it as obsolete and ignore its value for now. -
The above
Initialize
approach will likely not make things better for Civil3D. If I'm reading it correctly, they force create aPathManager
before they callAcDynamoModel.Start
, which instantiates ourDynamoModel
, so we lose the chance to properly callInitialize
with the right data. We may want to consider makingInitialize
override and recreate a new instance ofPathManager
. Crude, I know, but it would make things work for Civil3D. -
One step closer, great work! Please help close out PR #16460 🙂
Removed the isInitialized flag and added a lockObject, also removed the code from DynamoModel constructor and moved to the CreatePathManager method.
@benglin the PathManager Singleton implementation in the last commit was as suggested only I have to initialize inside the if(version != null) otherwise DynamoSandbox was crashing (due that version was null). |
For debugging purposes (when DynamoRevit is reading the info from Dynamo.config) this fix is needed otherwise when DynamoRevit calls GetDynamoPath method if the DynamoRevitVersion vs DynamoVersion doesn't match is returning null. Then with this fix DynamoPath will be always found and the Dynamo button will be shown in Revit.
} | ||
} | ||
|
||
public static PathManager Instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments for public property please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one comment @benglin I think this PR is pending your another look
@RobertGlobant20 Any tests you can add in this PR? |
Adding a test which verifies that the PathManager singleton instance is created with the expected properties (when using empty parameters).
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for accommodating the changes, @RobertGlobant20, good work as always! LGTM! 🚀
Purpose
Fix for installing Dynamo packages using the DynamoRevit version path (e.g. C:\Users<user>\AppData\Roaming\Dynamo\Dynamo Revit\27.0\packages)
After providing the context to GitHub Copilot for updating the PathManager lazy loading singleton for changing the PathManagerParams in the constructor (using HostAnalyticsInfo.HostVersion), the next alternatives were provided by Copilot:
In this PR is implemented the first one with some minor changes based in GitHub Copilot suggestions.
Declarations
Check these if you believe they are true
Release Notes
Fix for installing Dynamo packages using the DynamoRevit version path (e.g. C:\Users<user>\AppData\Roaming\Dynamo\Dynamo Revit\27.0\packages)
Reviewers
@QilongTang @zeusongit
FYIs