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

Mutable package-level vars are a poor choice for configuration #435

Closed
ash2k opened this issue Nov 4, 2022 · 6 comments · Fixed by #477
Closed

Mutable package-level vars are a poor choice for configuration #435

ash2k opened this issue Nov 4, 2022 · 6 comments · Fixed by #477

Comments

@ash2k
Copy link
Contributor

ash2k commented Nov 4, 2022

Hello there! I have a project where I get this library as a transitive dependency. It's used for "stuff X". I also want to use this library for "stuff Y", unrelated to "stuff X", in the same codebase/binary. I'd like to configure the library and modules differently for different use cases. But in a few places the library uses package-level variables for "global" configuration and I cannot do what I want.

Examples:

  • var NowFunc = time.Now
  • var Universe StringDict
  • var (
    AllowSet = false // allow the 'set' built-in
    AllowGlobalReassign = false // allow reassignment to top-level names; also, allow if/for/while at top-level
    AllowRecursion = false // allow while statements and recursive functions
    LoadBindsGlobally = false // load creates global not file-local bindings (deprecated)
    // obsolete flags for features that are now standard. No effect.
    AllowNestedDef = true
    AllowLambda = true
    AllowFloat = true
    AllowBitwise = true
    )
  • Maybe a few more places I didn't find now.

In addition to the above, global mutable variables are sometimes abused by third-party libraries. They assume they are the center of the world and mutate such variables in other packages in their init(). No mutable globals -> no such problems.

p.s. thank you for the library! It's great!

@adonovan
Copy link
Collaborator

adonovan commented Nov 4, 2022

Yes, global state is bad, and the decision to use it here assumes there's only one Starlark client app in the address space, which isn't necessarily true. The safer choice would be to have required extensible configuration structs as parameters to Parse, Resolve, Execute, etc, which is a nuisance but creates more leeway.

I suppose we could add configurable API variants to the existing code, and make the existing API read the globals and put them in the config struct before calling the new API.

NowFunc could be easily fixed by defining a Thread-local variable called "starlark.time.now" and making the time package use that if it exists. Then any client application can override the clock on a per-thread basis.

Universe never needs to be modified, since you can add to the Predeclared set in a hygienic way.

But the Allow* flags are unfortunate, I agree.

@ash2k
Copy link
Contributor Author

ash2k commented Jun 15, 2023

An alternative approach, which is probably a breaking change (or maybe this could be done as a "parallel" API) and I'm not saying it's better, is to introduce an "interpreter" object that is instantiated with all the language-affecting configuration provided (e.g. those Allow* flags). This object provides methods to spawn threads and do other things that need to (now or potentially in the future) use those global config flags (current or any future flags). This object (or something like it) can be injected into callbacks where this library calls use code. Just an idea.

@ash2k
Copy link
Contributor Author

ash2k commented Jun 15, 2023

For modules and for the "interpreter" object I'd suggest to have constructors with functional options, like what gRPC-go/etc does.

adonovan added a commit that referenced this issue Jun 15, 2023
This change eliminates the need for client applications
to set global variables to control dialect options,
as globals are error-prone and concurrency hostile.

All relevant API functions Foo now have a variant FooOptions
that takes an explicit FileOptions; the original function
accesses the legacy options by reading the global variables.

TODO: needs more doc comments

Fixes #435
adonovan added a commit that referenced this issue Jun 15, 2023
This change eliminates the need for client applications
to set global variables to control dialect options,
as globals are error-prone and concurrency hostile.

All relevant API functions Foo now have a variant FooOptions
that takes an explicit FileOptions; the original function
accesses the legacy options by reading the global variables.

TODO: needs more doc comments

Fixes #435
@adonovan
Copy link
Collaborator

It would be reasonable to construct a parallel API that used pure functions (not globals) for configuration and accepted a FileOptions struct whose zero value embodied the defaults. Each syntax.File would retain its options so that you wouldn't need to repeat it for later stages.

I've sketched it in #477. Please download the patch and try it out and let me know if it works for you.

adonovan added a commit that referenced this issue Jun 15, 2023
This change eliminates the need for client applications
to set global variables to control dialect options,
as globals are error-prone and concurrency hostile.

All relevant API functions Foo now have a variant FooOptions
that takes an explicit FileOptions; the original function
accesses the legacy options by reading the global variables.

TODO: needs more doc comments

Fixes #435
@ash2k
Copy link
Contributor Author

ash2k commented Jun 16, 2023

Looks good to me! I'll try it next week or so. I think it is what's needed. Thank you!

@ash2k
Copy link
Contributor Author

ash2k commented Jun 29, 2023

I've tried the branch, it works well for my needs. The unaddressed bit is the module (or modules?) that has a global var as config. Thanks!

adonovan added a commit that referenced this issue Aug 7, 2023
This change eliminates the need for client applications
to set global variables to control dialect options,
as globals are error-prone and concurrency hostile.

All relevant API functions Foo now have a variant FooOptions
that takes an explicit FileOptions; the original function
accesses the legacy options by reading the global variables.

Fixes #435
adonovan added a commit that referenced this issue Aug 14, 2023
This change eliminates the need for client applications
to set global variables to control dialect options,
as globals are error-prone and concurrency hostile.

All relevant API functions Foo now have a variant FooOptions
that takes an explicit FileOptions; the original function
accesses the legacy options by reading the global variables.

Fixes #435
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 a pull request may close this issue.

2 participants