-
Notifications
You must be signed in to change notification settings - Fork 280
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
Bind PackageServer to ephemeral port to avoid port conflicts #227
Conversation
9bc511f
to
17b25d9
Compare
e8b8074
to
e210fe9
Compare
46c6edd
to
a370e41
Compare
99efc41
to
d06dfb7
Compare
@bioball Rebased and ready for review. |
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.
Overall, this is looking great! I have some minor comments.
.single() | ||
.int() | ||
.default(-1) | ||
.validate { it in 1024..65535 } |
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.
Doesn't seem very useful to validate given it's a test flag, and it's also provisioned by the OS. Do we know it's going to be in this range?
.validate { it in 1024..65535 } |
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.
Not terribly important to have this validated, but ephemeral ports are guaranteed to be high ports (1024-65535).
https://en.wikipedia.org/wiki/Ephemeral_port#Range
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.
Gotcha, thanks for the link. Let's still remove this, though; it adds some unnecessary brittleness to our tests.
* | ||
* <p>If set, requests that specify port 12110 are rewritten to use the given port. This enables | ||
* users of this client to connect to a test server without having to know its (ephemeral) | ||
* listening port. |
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.
* listening port. | |
* listening port. | |
* | |
* <p>This is an internal option used for testing purposes only. |
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.
Calling this option "internal" felt strange because HttpClient
is a public API and we don't have a way to hide testPort
. It's also harmless. Should replace 12210 with 0 in a follow-up PR.
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.
It's the same with org.pkl.commons.cli.CliBaseOptions
; both are public APIs but expose flags that are only meant for test purposes (e.g. testMode
, testPort
added by this PR) and we call them "internal" in the doc comments.
uri.getQuery(), | ||
uri.getFragment()); | ||
} catch (URISyntaxException e) { | ||
throw new AssertionError(e); // only port changed |
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.
throw new AssertionError(e); // only port changed | |
throw PklBugException.unreachableCode(); // only port changed |
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.
Done. org.pkl.core.util
depending on org.pkl.core
seems problematic. But since PklBugException
is already used in org.pkl.core.util
, it's not making things worse.
@@ -49,6 +50,7 @@ public class ExecutorOptions2 extends ExecutorOptions { | |||
* @param projectDir API equivalent of the {@code --project-dir} CLI option. | |||
* @param certificateFiles API equivalent of the {@code --ca-certificates} CLI option | |||
* @param certificateUris API equivalent of the {@code --ca-certificates} CLI option | |||
* @param testPort API equivalent of the {@code --test-port} CLI option |
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.
* @param testPort API equivalent of the {@code --test-port} CLI option | |
* @param testPort Internal option used for testing purposes. |
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.
Removed this param from the public API.
@@ -62,7 +64,8 @@ public ExecutorOptions2( | |||
/* @Nullable */ Path moduleCacheDir, | |||
/* @Nullable */ Path projectDir, | |||
List<Path> certificateFiles, | |||
List<URI> certificateUris) { | |||
List<URI> certificateUris, | |||
int testPort) { |
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.
Since testPort
is an internal test-only option, we should have another constructor that doesn't have it as an option, and sets testPort
to -1
.
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.
Should probably address the root cause and provide a builder for ExecutorOptions2
. For example, almost nobody will want to pass certificates either.
I thought about modernizing the pkl-executor
API to look more like Evaluator
and language bindings. But then I realized that my time is better spent on Java language bindings, which have the potential to make pkl-executor
unnecessary.
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.
Added a builder and removed testPort
from public API. See commit message for details.
@bioball Ready from my side. See latest commit message for details. Quite happy with the cleanup. |
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.
Just a couple minor comments, but this is pretty close!
public static Builder builder() { | ||
return new Builder(); | ||
} | ||
|
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.
Good idea with the builder!
We have an existing pattern of having unconfigured()
and preconfigured()
builders; see EvaluatorBuilder
, ConfigEvaluatorBuilder
, ValueMapperBuilder
, etc. I think let's follow that.
public static Builder builder() { | |
return new Builder(); | |
} | |
// keep in sync with SecurityManagers.defaultAllowedModules | |
/// Returns the allowed module patterns that the CLI uses by default. | |
public static final List<String> defaultAllowedModules = | |
List.of( | |
"repl:", | |
"file:", | |
"jar:file:", | |
"modulepath:", | |
"https:", | |
"pkl:", | |
"package:", | |
"projectpackage:" | |
); | |
// keep in sync with SecurityManagers.defaultAllowedResources | |
/// Returns the allowed resources patterns that the CLI uses by default. | |
public static final List<String> defaultAllowedResources = | |
List.of( | |
"prop:", | |
"env:", | |
"file:", | |
"modulepath:", | |
"package:", | |
"projectpackage:", | |
"https:"); | |
/** | |
* Creates a builder that has no options set. | |
*/ | |
public static Builder unconfigured() { | |
return new Builder(); | |
} | |
/** | |
* Creates a builder configured with: | |
* | |
* <ul> | |
* <li>{@link #defaultAllowedModules}</li> | |
* <li>{@link #defaultAllowedResources}</li> | |
* <li>System environment variables</li> | |
* <li>System properties</li> | |
* </ul> | |
*/ | |
public static Builder preconfigured() { | |
//noinspection unchecked,rawtypes | |
return unconfigured() | |
.allowedModules(defaultAllowedModules) | |
.allowedResources(defaultAllowedResources) | |
.environmentVariables(System.getenv()) | |
.externalProperties((Map) System.getProperties()); | |
} |
.single() | ||
.int() | ||
.default(-1) | ||
.validate { it in 1024..65535 } |
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.
Gotcha, thanks for the link. Let's still remove this, though; it adds some unnecessary brittleness to our tests.
* | ||
* <p>If set, requests that specify port 12110 are rewritten to use the given port. This enables | ||
* users of this client to connect to a test server without having to know its (ephemeral) | ||
* listening port. |
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.
It's the same with org.pkl.commons.cli.CliBaseOptions
; both are public APIs but expose flags that are only meant for test purposes (e.g. testMode
, testPort
added by this PR) and we call them "internal" in the doc comments.
According to the Javadoc, Apart from adding
Removed.
There is a big difference: |
I don't see any issues with the set of options provided by |
What is |
d32d02c
to
0cb0030
Compare
This is a comprehensive solution to the "flaky PackageServer tests" problem. It rules out port conflicts and imposes no limits on test parallelism. The same solution can be used for other test servers in the future. Major changes: - Turn `PackageServer` from a singleton into a class that is instantiated per test class or test method. - Start the server the first time its `port` property is read. Bind the server to an ephemeral port instead of port 12110. - For every test that uses `PackageServer`, pass the server port to `--test-port`, `HttpClient.Builder.setTestPort`, the `CliBaseOptions` or `ExecutorOptions` constructor, or the Gradle plugin's `testPort` property. Wire all of these to `RequestRewritingClient`'s `testPort` constructor parameter. - Enhance `RequestRewritingClient` to replace port 12110 with `testPort` in request URIs unless `testPort` is -1 (its default). - Introduce `ExecutorOptions.Builder`. This makes executor options more comfortable to create and allows to hide options such as `testPort`. - Deprecate the `ExecutorOptions` constructor to steer users towards the builder. - Get rid of `ExecutorOptions2`, which is no longer needed. - Clean up `EmbeddedExecutorTest` with the help of the builder.
0cb0030
to
d61e6ee
Compare
I've made 2 out of 3 requested changes and have squashed my commits. I'll leave any builder changes to you. I've also rebased my Gradle 8.6 PR, which I'd love to land by the end of the week. |
The executor API is typically meant for services that need to evaluate 3rd party Pkl code. For instance, if you are a service that accepts Pkl files as configuration. And, like you said, in those cases, you wouldn't want to pass your own env vars or system properties to Pkl. The core thing that the executor provides that Thought about this a little more--yeah, there's probably not much of a use-case for |
Here is my take on how to best solve the "flaky PackageServer tests" problem.
It is ready to be merged after #217 .
See commit message for details.
If you prefer a different solution, perhaps this can serve as inspiration.