Skip to content

Reading configuration from config files#1722

Merged
gbrail merged 7 commits intomozilla:masterfrom
FOCONIS:rhino-config
Feb 12, 2025
Merged

Reading configuration from config files#1722
gbrail merged 7 commits intomozilla:masterfrom
FOCONIS:rhino-config

Conversation

@rPraml
Copy link
Copy Markdown
Contributor

@rPraml rPraml commented Nov 8, 2024

This could be a first draft for #1720

@rPraml rPraml marked this pull request as draft November 8, 2024 09:10
@gbrail
Copy link
Copy Markdown
Collaborator

gbrail commented Nov 13, 2024

I think that this approach makes sense in that it lets configuration come from the environment, system properties, file, or classpath. I think that's a good thing and that we should follow this pattern.

For actually checking the setting of various flags, however, I think that the config class should parse all this input and set boolean (or enum) values that can be checked directly, rather than relying on a hash lookup. I think that can give us two advantages:

  1. It keeps all the logic about naming and renaming things in one place
  2. It will be faster, since many of these properties, like the various debug flags, will be checked many millions of times potentially while Rhino runs, and all those hash table lookups will dominate performance pretty soon.

@rPraml
Copy link
Copy Markdown
Contributor Author

rPraml commented Nov 20, 2024

I think I can implement this next week or so...

@gbrail
Copy link
Copy Markdown
Collaborator

gbrail commented Nov 20, 2024

If you're looking for the kinds of debug flags I'd like to potentially replace with a real debugging mechanism, I would look at these:

public static final boolean printTrees = false;

private static final boolean DEBUGSTACK = false;

And as for feature flags, the first one IMO should be for the reflect and proxy support that's currently languishing in a PR by @rbri

@rPraml
Copy link
Copy Markdown
Contributor Author

rPraml commented Dec 13, 2024

Unfortunately, I didn't have as much time as I thought, but I wanted to update the draft before Christmas of how I want to do it now.

The idea is, to check all sources (classpath, configfile, system-properties, env - the last one wins) for the settings and parse them immediately in the correct datatype. When a variable is bound to the property rhino.debugLinker for example. I try to check also the upper-camel RHINO_DEBUG_LINKER version.

@gbrail I hope, this goes in the right direction... Unfortunately (or fortunately) I won't be able to continue until next year due to vacation. :)

@rPraml
Copy link
Copy Markdown
Contributor Author

rPraml commented Jan 17, 2025

So, I found some time to update this PR.

What do we have now

The RhinoProperties class is a container of serveral config maps, that are loaded from different location:

  • looks for rhino.config & rhino-test.config in several locations. (CHECKME: do we need rhino-test.config - similar to logback-test.xml?)
  • add system-env & system-properties
  • or optional: Implement RhinoConfigLoader service with your own loading strategy

When checking for a value, RhinoProperties tries all configs in loaded order in exact spelling and camel case-conversion. (e.g rhino.printICode and RHINO_PRINT_ICODE - you can use both writings in any config source. (CHECKME: This camel case conversion is required for system-env. Do we want it for other config sources, too? It theoretically slows down the parse time, as several permutations are checked, but every config is only parsed once, if assigned to a static field, so this should not have a big impact)

There is a helper class RhinoConfig which should be used in most cases. It adds some helper methods to get properties as String, Integer or Enum.

For actually checking the setting of various flags, however, I think that the config class should parse all this input and set boolean (or enum) values that can be checked directly, rather than relying on a hash lookup. I think that can give us two advantages:

  1. It keeps all the logic about naming and renaming things in one place

  2. It will be faster, since many of these properties, like the various debug flags, will be checked many millions of times potentially while Rhino runs, and all those hash table lookups will dominate performance pretty soon.

I tried that, but did not like the idea to add configs from different packages in one class:

  1. This is not optimal, if we want to split rhino in more modules. I can imagine to have one config class per module or package (please give feedback)
  2. This should be no problem in the current PR. Each config is assigned to a static constant, so it is read only one time.

Other findings, I identified:

  • I tried to attach the current config to the current context (so to have one context with PRINT_ICODE and an other without), but this would require to pass the context/config object down to Token etc. So I think, we have to accept, that we have only ONE config per classloader and every rhino context uses the same settings. It is important in the future, to decide, if some config values should be global or at context level.
  • I read all properties (also system-properties & env). So someone that have access to this class will effectively bypass the (deprecated) security-manager

it would be good, if I get some feedback, if this goes in a way we want.

@gbrail FYI

@rPraml
Copy link
Copy Markdown
Contributor Author

rPraml commented Jan 23, 2025

@gbrail ping... Did you find already time to give some feedback?
Thanks

Copy link
Copy Markdown
Collaborator

@gbrail gbrail left a comment

Choose a reason for hiding this comment

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

I think this is good progress, and minus the attached minor comments, I think that this is the right implementation.

I also want to know how we can test this -- there are a lot of options with properties files, system properties, environment variables, and a service loader.

I really think that we should go forward with this, but only if we can put in some way to exercise more of this codebase in our test suite.

if (ret != null) {
Class<T> enumType = (Class<T>) defaultValue.getClass();
// We assume, that enums all are in UPPERCASES
return Enum.valueOf(enumType, ret.toString().toUpperCase(Locale.ROOT));
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should we catch "IllegalArgumentException" in case the input value is not valid and return the default in that case?

if (ret instanceof Boolean) {
return (Boolean) ret;
} else {
return "1".equals(ret) || "true".equals(ret);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I wonder if you'd consider numeric values other than 1 as "true," and doing a case-insensitive comparison to "true"

props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
addConfig(props);
} catch (IOException e) {
System.err.println(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't love that we "println" anywhere, but I don't see that we have a choice in this case. But if it happens, it'll be buried in someone's big log file and they won't know what it means -- should we consider adding "Rhino:" or something to identify this message so that people can know why it's appearing?

props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
addConfig(props);
} catch (IOException e) {
System.err.println(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same here -- we should add "Rhino" or something to this.

@rPraml rPraml force-pushed the rhino-config branch 2 times, most recently from c09c606 to 5799ee2 Compare February 3, 2025 11:08
Copy link
Copy Markdown
Contributor Author

@rPraml rPraml left a comment

Choose a reason for hiding this comment

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

From my perspective, this is ready for merge.

I would like to add some documentation to https://rhino.github.io
Can anyone tell me, how? (i did not find the repo/instruction how to contribute)

@gbrail FYI

public static void main(String args[]) {
try {
if (Boolean.getBoolean("rhino.use_java_policy_security")) {
if (RhinoConfig.get("rhino.use_java_policy_security", false)) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Q: Should we change this to

Suggested change
if (RhinoConfig.get("rhino.use_java_policy_security", false)) {
if (RhinoConfig.get("rhino.useJavaPolicySecurity", false)) {

A: Probably not, as this will break other things!
And JavaPolicySecurity has to be removed anyway in the future (see #1353)

private static String[] CONFIG_FILES = {"rhino.config", "rhino-test.config"};
private List<Map<?, ?>> configs = new ArrayList<>();
// this allows debugging via system property "rhino.debugConfig"
private final boolean debug = Boolean.getBoolean("rhino.debugConfig");
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've added this flag in a separate commit in this PR. It will help to trouble-shoot any loading issues. So I find this useful and would be happy, if we can keep this

* this module will also use this loader. <br>
* The loader does NOT load the properties from the default locations as mentioned in the
* documentation. So, do not wonder, if setting config values in 'rhino.config' or
* 'rhino-test.config' do not take effect. Instead use `rhino-config-for-this-module.config`.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Testing is a bit difficult, so I tried to cover all aspects in this tests

Comment thread tests/build.gradle
if (k.startsWith("rhino.")) {
systemProperty k, v
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I copy all rhino system properties to the sub process. So you can pass properties with -Drhino.xyz from a gradle buld

@rPraml rPraml changed the title Draft for reading configuration from config files Reading configuration from config files Feb 3, 2025
@rPraml rPraml marked this pull request as ready for review February 3, 2025 14:06
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this PR is looking very good, but why do we have this file and the other .config files in the top-level directory? I suppose that we need one just to test the file reading part of the codebase, but since they won't be used by the runtime or tests otherwise, do we really need all of them? That's really my only concern with this PR at this point. Thanks!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hello @gbrail

I also want to know how we can test this -- there are a lot of options with properties files, system properties, environment variables, and a service loader.

This is the problem, why I need so much test files. Let me explain for what they are.

rhino/rhino-config-for-this-module.config
The file rhino-config-for-this-module.config is used internally by RhinoPropertiesTest.Loader and is the only config-file loaded in this module for tests.
This file, along with rhino/rhino-test.config, serves as a placeholder for where properties should be placed.
For example, if there is a new experimental feature 'xyz' that can be enabled with rhino.experimental.xyz=true, it can be enabled in rhino-config-for-this-module.config or rhino-test.config, so that is available for test cases.
As these files are not included in the build jars, the feature stays disabled, unless someone enables it.

rhino/rhino-test.config
rhino/src/test/resources/rhino-testcase.config
rhino/src/test/resources/rhino.config
These files are used for various tests

tests/rhino-test.config
This file is mainly for documentation/example (there are only comments) and can be used to enable features for testing.

I see the problem, that this is now a bit confusing, but the files are required for the (complex) tests.

I see the following options:

  • keep everything as it is (this will confuse people)
  • create one (or more) rhino-config-tests module, to test various config ways (with/without ServiceLoader)
  • remove/rewrite the complex test cases (especially the one with serviceLoader), which means, that the one or other properties-file will be obsolete.

I will prefer option 3 ;)

Copy link
Copy Markdown
Contributor Author

@rPraml rPraml left a comment

Choose a reason for hiding this comment

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

I've refactored the tests a bit. There are currently two top level rhino-test.config files, which are mainly used for documentation (and one test)

I can delete these files, if you want.
Should I add the documentation to https://github.com/rhino/rhino.github.io/blob/main/docs/_docs/configuration.md or is there a better/preferred place?

Comment thread rhino/rhino-test.config
#
# Please keep this values for RhinoPropertiesTest
test.file.rhino-test-config.loaded=true
test.config.bar=value4-mod
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This file is a placeholder/for documentation and is also used to demonstrate, that a top-level file can override values in a classpath file.
Should I delete it (and the related test)?

Comment thread tests/rhino-test.config
# rhino.printTrees=false
# rhino.printICode=false
# rhino.debugLinker=false
# rhino.use_java_policy_security=false
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This file is really only a placeholder/for documentation. Should I delete it?

@gbrail
Copy link
Copy Markdown
Collaborator

gbrail commented Feb 8, 2025

Sorry, lost track of where in the thread to comment.

I think it's fine to leave "rhino.test-config" files in the "rhino" and "tests" modules. Could you please add a comment explaining why they're there, and say that it's an example of how to use these files, or that people could uncomment them in order to do further testing? Once you do that I think that we should merge this.

As for the docs, I think it'd be great if you updated the docs on "rhino.io". @p-bakker owns that repo and I'm sure he'd accept a PR.

@rPraml
Copy link
Copy Markdown
Contributor Author

rPraml commented Feb 10, 2025

Hello @gbrail I rebased this PR and made these changes: 28277a4

@gbrail
Copy link
Copy Markdown
Collaborator

gbrail commented Feb 11, 2025

Do you have time to look at the two warnings that come from ErrorProne?

/home/runner/work/rhino/rhino/rhino/src/main/java/org/mozilla/javascript/config/RhinoProperties.java:173: warning: [AnnotateFormatMethod] This method uses a pair of parameters as a format string and its arguments, but the enclosing method wasn't annotated @FormatMethod. Doing so gives compile-time rather than run-time protection against malformed format strings.
private void logDebug(String msg, Object... args) {
^
(see https://errorprone.info/bugpattern/AnnotateFormatMethod)
/home/runner/work/rhino/rhino/rhino/src/main/java/org/mozilla/javascript/config/RhinoProperties.java:180: warning: [AnnotateFormatMethod] This method uses a pair of parameters as a format string and its arguments, but the enclosing method wasn't annotated @FormatMethod. Doing so gives compile-time rather than run-time protection against malformed format strings.
private void logError(String msg, Object... args) {
^
(see https://errorprone.info/bugpattern/AnnotateFormatMethod)

I can look at them myself later if you'd like, but I'm trying to keep the code clean of those warnings so I'd like to see if you have a chance to fix the underlying issue first. Thanks!

@rPraml
Copy link
Copy Markdown
Contributor Author

rPraml commented Feb 11, 2025

Hello @gbrail,

tried to fix this, but com.google.errorprone.annotations.FormatMethod is not on the classpath.
I see two options:

  • add errorprone dependency (maybe in an extra PR?)
  • add @SuppressWarnings("AnnotateFormatMethod") in the meantime.

I can look at them myself later if you'd like, but I'm trying to keep the code clean of those warnings so I'd like to see if you have a chance to fix the underlying issue first. Thanks!

In this case I would accept the offer and leave the fix to you ;)

Roland

@gbrail
Copy link
Copy Markdown
Collaborator

gbrail commented Feb 11, 2025 via email

@gbrail
Copy link
Copy Markdown
Collaborator

gbrail commented Feb 12, 2025

Thanks for your work on this and I'll look at the warning soon.

@gbrail gbrail merged commit 34d9bec into mozilla:master Feb 12, 2025
rPraml added a commit to FOCONIS/rhino.github.io that referenced this pull request Feb 13, 2025
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.

2 participants