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

win,tty: fix read stop in line mode #866

Closed

Conversation

orangemocha
Copy link
Contributor

Closing the handle does not make ReadConsoleW exit reliably on Windows 7 and above. Thus, after switching from line to raw mode, keypresses were held until enter was pressed. This has caused a number of issues in node: nodejs/node#2504 , nodejs/node#2996, nodejs/node#5384 , nodejs/node#5821 , nodejs/node#5940 .

This fix makes ReadConsoleW exit by writing a return keypress to its input buffer, similar to what was already done for raw mode. The second commit removes the duplicate handle that was used to close the console before. The third commit restores the cursor position to where it was before writing the enter, because ReadConsoleW echoes the newline in the screen.

This PR includes a test that switches the console to raw mode and uses WriteConsoleInputW to test reading in raw mode. I do not know of a unix equivalent to WriteConsoleInputW, so the test is for Windows only. In this test, switching to raw mode is done after a 100 ms sleep, to allow uv_tty_line_read_thread to reach ReadConsoleW.

The added User32 library is necessary for MapVirtualKeyW.

Fixes: #852

cc @saghul @piscisaureus

@eljefedelrodeodeljefe
Copy link
Contributor

Who would have thought...

@@ -483,7 +483,6 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s);
union { \
struct { \
/* Used for readable TTY handles */ \
HANDLE read_line_handle; \
Copy link
Member

Choose a reason for hiding this comment

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

I think this will break the ABI because the size of struct rd is changed. Add a comment of it not being used anymore and we'll remove it for v2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call! Will fix.

@saghul
Copy link
Member

saghul commented May 12, 2016

Thanks a lot for working on this Alexis! I left some comments. There is one little important thing which definitely needs fixing: the ABI breakeage. Other than that, I don't have enough experience with low level Windows console handling to make a final judgement call, so I'll trust you and @piscisaureus.

@saghul saghul mentioned this pull request May 12, 2016
@orangemocha
Copy link
Contributor Author

Thanks for the feedback, @saghul ! I have added 3 commits to address your comments. I can fixup the original commits once the PR has passed review.

@saghul
Copy link
Member

saghul commented May 12, 2016

LGTM if the CI is green: https://ci.nodejs.org/job/libuv-test-commit-windows/48/ But I'd like @piscisaureus to take a look too, if possible.

@MylesBorins
Copy link
Contributor

MylesBorins commented May 12, 2016

it looks like the libuv CI is down... this is likely related to the updated that came in yesterday. /cc @jbergstroem

@saghul
Copy link
Member

saghul commented May 12, 2016

I just noticed, thanks for taking a look Myles!

@saghul
Copy link
Member

saghul commented May 12, 2016

To save anyone a trip to the console:

Started by upstream project "libuv-test-commit-windows" build number 50
originally caused by:
 Started by user Saúl Ibarra Corretgé
[EnvInject] - Loading node environment variables.
Building remotely on test-azure_msft-win2008r2-x64-2 (win2008r2-1p win2008r2 win2008r2-vs2015) in workspace c:\workspace\libuv-test-commit-windows\nodes\win2008r2
Wiping out workspace first.
Cloning the remote Git repository
Cloning repository https://github.com/$user/$project.git
 > git init c:\workspace\libuv-test-commit-windows\nodes\win2008r2 # timeout=10
Fetching upstream changes from https://github.com/$user/$project.git
 > git --version # timeout=10
 > git -c core.askpass=true fetch --tags --progress https://github.com/$user/$project.git +refs/heads/*:refs/remotes/origin/*
ERROR: Error cloning remote repo 'origin'
hudson.plugins.git.GitException: Command "git -c core.askpass=true fetch --tags --progress https://github.com/$user/$project.git +refs/heads/*:refs/remotes/origin/*" returned status code 128:
stdout: 
stderr: fatal: unable to access 'https://github.com/$user/$project.git/': The requested URL returned error: 400

    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:1719)
    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandWithCredentials(CliGitAPIImpl.java:1463)
    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.access$300(CliGitAPIImpl.java:63)
    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl$1.execute(CliGitAPIImpl.java:314)
    at org.jenkinsci.plugins.gitclient.CliGitAPIImpl$2.execute(CliGitAPIImpl.java:506)
    at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler$1.call(RemoteGitImpl.java:152)
    at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler$1.call(RemoteGitImpl.java:145)
    at hudson.remoting.UserRequest.perform(UserRequest.java:120)
    at hudson.remoting.UserRequest.perform(UserRequest.java:48)
    at hudson.remoting.Request$2.run(Request.java:332)
    at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:68)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at hudson.remoting.Engine$1$1.run(Engine.java:85)
    at java.lang.Thread.run(Unknown Source)
    at ......remote call to test-azure_msft-win2008r2-x64-2(Native Method)
    at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1416)
    at hudson.remoting.UserResponse.retrieve(UserRequest.java:220)
    at hudson.remoting.Channel.call(Channel.java:781)
    at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler.execute(RemoteGitImpl.java:145)
    at sun.reflect.GeneratedMethodAccessor529.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.jenkinsci.plugins.gitclient.RemoteGitImpl$CommandInvocationHandler.invoke(RemoteGitImpl.java:131)
    at com.sun.proxy.$Proxy101.execute(Unknown Source)
    at hudson.plugins.git.GitSCM.retrieveChanges(GitSCM.java:1057)
    at hudson.plugins.git.GitSCM.checkout(GitSCM.java:1097)
    at hudson.scm.SCM.checkout(SCM.java:485)
    at hudson.model.AbstractProject.checkout(AbstractProject.java:1269)
    at hudson.model.AbstractBuild$AbstractBuildExecution.defaultCheckout(AbstractBuild.java:607)
    at jenkins.scm.SCMCheckoutStrategy.checkout(SCMCheckoutStrategy.java:86)
    at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:529)
    at hudson.model.Run.execute(Run.java:1738)
    at hudson.matrix.MatrixRun.run(MatrixRun.java:146)
    at hudson.model.ResourceController.execute(ResourceController.java:98)
    at hudson.model.Executor.run(Executor.java:410)
ERROR: null
Notifying upstream projects of job completion
Finished: FAILURE

@MylesBorins
Copy link
Contributor

fingers crossed CI should be working now

ci: https://ci.nodejs.org/view/libuv/job/libuv-test-commit-windows/51/

I will keep this comment updated with the status of getting this working to avoid spamming the thread

@saghul
Copy link
Member

saghul commented May 12, 2016

Thanks Myles! Build is 💚

Closing the handle does not make ReadConsoleW exit reliably on
Windows 7 and above. Thus, after switching from line to raw mode,
keypresses were held until enter was pressed. This makes ReadConsoleW
exit by writing a return keypress to its input buffer, similar to
what was already done for raw mode.

Fixes: libuv#852
@orangemocha
Copy link
Contributor Author

Great, thanks! I applied the fixups, should be ready for landing.

@saghul
Copy link
Member

saghul commented May 13, 2016

Can you please reword the second commit? Now we are not removing anything :-)

I can work on a release tonight or on monday, but I guess nobody wants to release on a friday...

@MylesBorins
Copy link
Contributor

@saghul we usually do the node releases on Tuesdays... so as long as it is out the door Monday I see no reason it could make it into next weeks v6 release

Since we cancel ReadConsole by sending a newline, the duplicate
handle is no longer necessary.
@saghul
Copy link
Member

saghul commented May 13, 2016

Thanks Alexis!

@libuv/collaborators can I get another pair of eyes on this?
On May 13, 2016 11:25, "Alexis Campailla" notifications@github.com wrote:

Second commit reworded.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#866 (comment)



/* Null uv_buf_t */
static const uv_buf_t uv_null_buf_ = { 0, NULL };

static CRITICAL_SECTION uv__read_console_trap_lock;
static volatile BOOL uv__read_console_trap = FALSE;
Copy link
Contributor

Choose a reason for hiding this comment

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

There is no need to mark these volatile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are probably right, but in my (long, possibly dated) experience with VC++ it's safer to rely on explicit volatile declarations. It should be noted that the volatile semantics are MS-specific: https://msdn.microsoft.com/en-us/library/12a04hfd.aspx.
It's possible that the compiler has gotten smarter and knows not to reorder accesses around critical sections, but is there really much harm in making it explicit?

@orangemocha
Copy link
Contributor Author

@piscisaureus, I updated the last commit to address most of your feedback. PTAL.
You can view the incremental fixup here, should that make it easier for you to review.

I removed the wait from the main thread, and made the ReadConsole thread restore the console state. This should be safe and as you noted, it’s best not to have any waits in the main thread.

I also added some logic to handle the simple scroll case. If the cursor was at the bottom of the screen buffer, then we can assume that the ENTER we sent caused the screen buffer to scroll up of one line, so we adjust accordingly. Note that I am not handling the case in which the screen buffer scrolled up of more than one line since we called GetConsoleScreenBufferInfo, which would be hard to detect and is hopefully very unlikely in practice. Even if we hit that case, we would end up with the cursor one line below where it should be, which should essentially have the same effect of the ENTER being visible.

I also simplified the synchronization logic by using one status variable and interlocked operations.

@saghul
Copy link
Member

saghul commented May 16, 2016

@piscisaureus I plan to release libuv with this fix (one you approve it) tonight, if you can get it reviewed by then it would be great.

[]

DWORD written;
DWORD err = 0;
LONG status;

Copy link
Contributor

Choose a reason for hiding this comment

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

assert(!(handle->flags & UV_HANDLE_CANCELLATION_PENDING))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can address this now

@piscisaureus
Copy link
Contributor

@piscisaureus, I updated the last commit to address most of your feedback. PTAL.
You can view the incremental fixup here, should that make it easier for you to review.

I removed the wait from the main thread, and made the ReadConsole thread restore the console state. This should be safe and as you noted, it’s best not to have any waits in the main thread.

Great!

I also added some logic to handle the simple scroll case. If the cursor was at the bottom of the screen buffer, then we can assume that the ENTER we sent caused the screen buffer to scroll up of one line, so we adjust accordingly. Note that I am not handling the case in which the screen buffer scrolled up of more than one line since we called GetConsoleScreenBufferInfo, which would be hard to detect and is hopefully very unlikely in practice.

Even if we hit that case, we would end up with the cursor one line below where it should be, which should essentially have the same effect of the ENTER being visible.

Sounds good to me.

I also simplified the synchronization logic by using one status variable and interlocked operations.

First of all, good job, it's much cleaner this way.
I think the amount of redundant state could be reduced even further, and the fact that the restore position needs another synchronized variable is a bit suspect to me.

But it's a the fine line between diligent and nitpicky, and many users are clamoring for a fix.
So LGTM, go forth and land.

When we send VK_RETURN to make ReadConsole return, a spurious new line
is echoed to the screen. This is pretty visible in Node.js, since it
calls uv_tty_read_start() and uv_tty_read_stop() in rapid succession
during startup.

With this change, we save the screen state just before sending
VK_RETURN, and restore the cursor position as soon as ReadConsole
returns.
@orangemocha
Copy link
Contributor Author

Added the assert.

@orangemocha
Copy link
Contributor Author

@saghul
Copy link
Member

saghul commented May 16, 2016

CI is 💚 and Bert gave us his blessing, so I'm landing this.

saghul pushed a commit that referenced this pull request May 16, 2016
Closing the handle does not make ReadConsoleW exit reliably on
Windows 7 and above. Thus, after switching from line to raw mode,
keypresses were held until enter was pressed. This makes ReadConsoleW
exit by writing a return keypress to its input buffer, similar to
what was already done for raw mode.

Fixes: #852
PR-URL: #866
Reviewed-by: Bert Belder <bertbelder@gmail.com>
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
saghul pushed a commit that referenced this pull request May 16, 2016
Since we cancel ReadConsole by sending a newline, the duplicate
handle is no longer necessary.

PR-URL: #866
Reviewed-by: Bert Belder <bertbelder@gmail.com>
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
saghul pushed a commit that referenced this pull request May 16, 2016
When we send VK_RETURN to make ReadConsole return, a spurious new line
is echoed to the screen. This is pretty visible in Node.js, since it
calls uv_tty_read_start() and uv_tty_read_stop() in rapid succession
during startup.

With this change, we save the screen state just before sending
VK_RETURN, and restore the cursor position as soon as ReadConsole
returns.

PR-URL: #866
Reviewed-by: Bert Belder <bertbelder@gmail.com>
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
@saghul
Copy link
Member

saghul commented May 16, 2016

Landed in e51442b, 349aa6c and 9eb1311. Thanks Alexis and Joao for the patches, and Bert for the review!

@saghul saghul closed this May 16, 2016
@orangemocha
Copy link
Contributor Author

Awesome! Thanks a lot for the reviews and all the help, @saghul and @piscisaureus !

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.

tty issues when switching to raw mode on Windows
7 participants