Skip to content
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

Fixes several issues found by the new Grapher module work #105

Merged
merged 9 commits into from
Sep 3, 2024

Conversation

schuylermartin45
Copy link
Collaborator

@schuylermartin45 schuylermartin45 commented Aug 26, 2024

This PR introduces a few things:

  1. A new DependencyVariable type to track dependencies we can't immediately resolve with MatchSpec. This class shares the name property with MatchSpec to ensure some interoperability between the two types.

This came out of an issue with some variadic dependencies like {{ compiler("c") }}. When constructed with MatchSpec, such dependency strings became extremely corrupted.

  1. Unrecognized (i.e. not set in the recipe file) JINJA-variables are now $-sign escaped when get_value() is called with sub_vars = True.

This is done for a few reasons:

  • It indicates the variable was not render-able
  • The value is YAML-parseable/more compliant with the V1 spec
  1. The SentinelType is now pickle-able so that the RecipeParser classes can be safely used with the multiprocessing library, without risking data corruption. A new regression test has been added to attempt to catch future pickling issues.

…ign escaped when get_value() is called with the sub_vars flag
@schuylermartin45 schuylermartin45 changed the title Addresses issue where variables that can't be rendered are 569XZilmss… Addresses issue where JINJA vars that can't be rendered Aug 26, 2024
@schuylermartin45
Copy link
Collaborator Author

I put this PR up as a draft to see if the issue I've been having with multiprocessing was a platform-specific bug.

After more digging, it looks like pickling the RecipeParser class (and its derivatives) causes a corruption in the parse tree. I think multiprocessing must be pickling object instances when using map or starmap, as it exhibits the exact same corruption.

The other instances of map/starmap in this project return strings and simple data structures. The new RecipeGraphFromDisk constructor is the first to return a RecipeParser instance from a parallelized map call. Hence, why we're discovering this now.

@schuylermartin45 schuylermartin45 marked this pull request as ready for review August 27, 2024 14:28
@schuylermartin45 schuylermartin45 requested a review from a team as a code owner August 27, 2024 14:28
@schuylermartin45 schuylermartin45 changed the title Addresses issue where JINJA vars that can't be rendered Fixes several issues found by the new Grapher module work Aug 27, 2024
@schuylermartin45
Copy link
Collaborator Author

PR is ready to review, this significantly improves our ability to construct comprehensive RecipeGraphs

Comment on lines 39 to 53
"""
A single sentinel class to be used in this project, as an alternative to `None` when `None` cannot be used.
It is defined in a way such that SentinelType instances survive pickling and allocations in different memory
spaces.
"""

def __eq__(self, o: object) -> bool:
"""
Provides a pickle/thread-safe way to compare objects to the Sentinel Type. All sentinels are equal by simply
being sentinel objects.
:param o: The object to compare against.
:returns: True if the object is a SentinelType object.
"""
# There is an implicit `isinstance(self, SentinelType) and ...` to this logic
return isinstance(o, SentinelType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest reusing some of the sentinel implementations that exist out there, unittest.mock has one, there is https://github.com/Infinidat/sentinels or https://github.com/eddieantonio/sentinel and probably many more.

I don't think your implementation covers the full list of qualities of a sentinel value (e.g. _sentinel isn't able to be compared with is), even though it might fit this particular PR's needs. Nothing wrong with sentinels generally, of course, they are a great pattern.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've struggled to find a standard approach here. I would prefer to use something from the standard library, but the mocking version looks specifically designed for testing. I don't think I want unit test helpers in production code.

The other two libraries listed look like they haven't had a release in a few years and I'm unsure what level of support we can expect.

is is not used in any of the sentinel checks that currently exist. For our purposes, the current solution may be good enough for now. We need some kind of sentinel for the RecipeParser, there are a number of places I need to discern the difference between None/Null and "another default of northing".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice if this was accepted: https://peps.python.org/pep-0661/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice if this was accepted: peps.python.org/pep-0661

True!

FWIW, I was suggesting to simply copy over the code, not rely on it as a dependency. They are 3-clause BSD and MIT licensed, so are pretty simple to include, as long as the license statement is kept.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, it was really just a suggestion, not a suggestion wink wink :) I'll have more time to finish the review tomorrow!

:returns: True if the object is a SentinelType object.
"""
# There is an implicit `isinstance(self, SentinelType) and ...` to this logic
return isinstance(o, SentinelType)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

class X:
    def __new__(cls):
        global singleton
        try:
            return singleton
        except NameError:
            singleton = super().__new__(cls)
            return singleton

>>> x = X()
>>> x2 = X()
>>> x is x2
True
>>> import pickle
>>> x3 = pickle.loads(pickle.dumps(x2))
>>> x3 is x
True

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is in-line with the libraries that @jezdez suggested. But it's also pretty simple, which I like. You've also reminded me that I forgot to add a unit test to pickle this type directly. Thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me take a stab at implementing this version.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to make a few modifications to make the static analyzer and linter happy, but it appears to work!

@schuylermartin45 schuylermartin45 merged commit 26eab68 into main Sep 3, 2024
12 checks passed
@schuylermartin45 schuylermartin45 deleted the smartin_fix_sub_vars_crash branch September 3, 2024 16:31
schuylermartin45 added a commit that referenced this pull request Sep 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants