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

wait for tcp port available (win32) #2

Open
wants to merge 39 commits into
base: windows
Choose a base branch
from

Conversation

dmanto
Copy link

@dmanto dmanto commented Mar 31, 2021

Wait for server ready on windows platforms. This helps to allow a minimal windows platforms support. Related to issue #1

@kraih
Copy link
Member

kraih commented Mar 31, 2021

This module follows the normal Mojolicious rules. So tests and documentation are still required.

@kraih
Copy link
Member

kraih commented Mar 31, 2021

Why is this a Windows only feature? I don't see any Windows specific code. It is actually counter to the whole argument for adding the feature in the first place. We could offer it for Windows, macOS and Linux., as a slightly less safe alternative to passing the file descriptor directly to the managed process.

@@ -90,7 +92,23 @@ class Server extends EventEmitter {
});

// Should be resolved by the "spawn" event, but that is only supported in Node 15+
return Promise.resolve;
if (process.platform !== 'win32') return Promise.resolve;
return new Promise((resolve) => {
Copy link
Member

Choose a reason for hiding this comment

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

There is no reject handler. What's supposed to happen when it times out?

Copy link
Author

Choose a reason for hiding this comment

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

the caller will do exactly the same (trying to connect), and will fail because the port is not ready.

Copy link
Member

Choose a reason for hiding this comment

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

A promise does not resolve on its own.

lib/server-starter.js Outdated Show resolved Hide resolved
lib/server-starter.js Outdated Show resolved Hide resolved
(function loop () {
const connection = net.connect(port, resolve);
connection.on('error', err => {
if (err.code === 'ECONNREFUSED') {
Copy link
Member

Choose a reason for hiding this comment

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

And if it's something else?

Copy link
Author

Choose a reason for hiding this comment

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

should resolve the promise. But it actually doesn't, my mistake, see my answer about the logic a few minutes ago

Copy link
Member

Choose a reason for hiding this comment

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

It should reject the promise, not resolve.

Copy link
Author

Choose a reason for hiding this comment

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

The only thing the loop does is to wait enough time to get the server in a defined (either right or wrong) state. So if there is an error different from ECONNREFUSED there is no need to stay waiting, there is no argue on that.

But I think there is no added value in reject the promise with an error either. This is because you will try to connect by yourself after the launch of the server, and you will be sucesfull or not, and get the right error in any case.

Anyway the promise could be rejected when there is an error detected, the change itself is trivial (although the specific tests are not that easy) but I cannot think of any use case in which this can be beneficial for the user of the module.

const connection = net.connect(port, resolve);
connection.on('error', err => {
if (err.code === 'ECONNREFUSED') {
if (new Date() < timeToStop) {
Copy link
Member

Choose a reason for hiding this comment

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

And if it times out we just do nothing?

Copy link
Author

Choose a reason for hiding this comment

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

yes the caller will take care, I mean will recive the exact same error

Copy link
Member

Choose a reason for hiding this comment

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

How does the error reach the caller?

Copy link
Author

@dmanto dmanto Mar 31, 2021

Choose a reason for hiding this comment

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

The logic is like:

  • wait up to win32Timeout mS, as long as the error you are getting is ECONNREFUSED.
  • if you could connect, resolve the promise
  • if you get other error, you will not retry

so Ooops, I need to resolve at the end of the anonymous function (or reject, but that will be handled much better by the caller, when it tries to connect)

@@ -64,12 +64,14 @@ class Server extends EventEmitter {
* @param {object} [options] - Optional settings
* @param {boolean} [options.stdout=false] - Forward server output from STDOUT to STDOUT
* @param {boolean} [options.stderr=true] - Forward server output from STDERR to STDERR
* @param {number} [options.win32Timeout=30000] - (Windows only) Max time to wait for server ready, in mS
Copy link
Member

Choose a reason for hiding this comment

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

As far as i can see there is no reason at all for this to be Windows specific.

Copy link
Author

Choose a reason for hiding this comment

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

It will have no effect on other platforms, at least how is it written.

If you mean to wait for connection ready at the TCP port in all cases, it could be done.

Copy link
Member

Choose a reason for hiding this comment

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

The way it is done now makes little sense. Since the module doesn't become more portable, it just allows someone to use it in a Windows specific way. Instead of enabling one test to run on all platforms.

@kraih
Copy link
Member

kraih commented Mar 31, 2021

So, to sum up the requirements. a) The feature needs to work on all platforms, Windows, macOS and Linux. b) There needs to be an additional documentation section explaining how to use the module in a portable (but slightly less safe) way. c) There need to be new tests for success (random and specific port) and failure cases (timeout, failed web app start, connection error other than ECONNREFUSED).

@dmanto dmanto closed this Apr 4, 2021
@dmanto dmanto reopened this Apr 5, 2021
@dmanto
Copy link
Author

dmanto commented Apr 6, 2021

  • I hope we still have a chance to get to a usable feature on this issue.
  • And as the other PR was a mistake, so we leave it closed, but I will address all issues mentioned by you in the following points.
  • Regarding your sum up on requirements above:
    a) The feature should work in Linux, macOS and Windows: done, and tested on all of them,
    b) Additional documentation: done, added a section on launching servers with this "portable" feature,
    c) New tests: (all of them in a new script: test/start_port.js, which is a modified version of test/start_fd.js)
    - success (random port): done,
    - success (specific port): done,
    - timeout, done (reduced to 500 mS so we don't slow down tests too much),
    - failed web app start: done, we got a timeout plus server emitted specific error,
    - connection error other than ECONNREFUSED: done, we got a timeout plus server emitted specific error
    Also regarding to tests, I needed a way to skip fd tests on windows, so I used some tap functionality to skip them all on win32 platforms. It may look hacky, so just let me know if you know a cleaner way.
  • About the "no reject handler" remark: the reject case was added so is fixed now (done). Also I want to apologize for my misunderstanding on the process, I answered in the conversation with some explanation in the timeout logic and thought that was enough, that's why I included the comment in the code (In no way I meant to offend you nor to ignore any of your comments).
  • About the "this constant does nothing" remark, sorry I had missed your last comment, is fixed now (done).
  • About the "me still missing the point" as this is not meant to be a Mojolicious specific module, I think I understand that. I removed the ENV variable used to avoid fd passing, and also the "listenAddress()" function, so to avoid any "Hidden magic" as you mentioned on other remark.
  • To let the customer choose how to launch the child server process, a new launch function, launchPortable(), is defined.
    The reason I separate both cases is that the logic changes a lot between them, as launchPortable needs the port to be closed before spawning the server, otherwise the "first" race condition will trigger a lot (almost always on a mac). launch() in the other hand, needs to close the port after the server opened the passed fd. And for the retry and timeout functionality, I cannot see any case in which that functionality would make sense when using fd passing, as the port should be up all the time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants